diff --git a/.github/workflows/IKVM.yml b/.github/workflows/IKVM.yml
index 8adeee8014..d7e07166a1 100644
--- a/.github/workflows/IKVM.yml
+++ b/.github/workflows/IKVM.yml
@@ -15,20 +15,20 @@ on:
jobs:
build-openjdk:
name: Build OpenJDK
- runs-on: ubuntu-18.04
+ runs-on: ubuntu-latest
steps:
- name: Checkout Source
- uses: actions/checkout@v1
+ uses: actions/checkout@v3
with:
submodules: recursive
- name: Cache OpenJDK Build
uses: actions/cache@v3
with:
path: openjdk/build/linux-x86_64-normal-server-release
- key: openjdk-build-linux-x86_64-normal-server-release--${{ runner.os }}--${{ hashFiles('openjdk/**', '!openjdk/build') }}-8
+ key: openjdk-build-linux-x86_64-normal-server-release--${{ runner.os }}--${{ hashFiles('openjdk/**', '!openjdk/build') }}-10
- name: Check OpenJDK Build Stamp
id: openjdk-build-stamp
- uses: andstor/file-existence-action@v1
+ uses: andstor/file-existence-action@v2
with:
files: openjdk/build/linux-x86_64-normal-server-release/stamp
- name: Fetch OpenJDK 7
@@ -74,13 +74,7 @@ jobs:
- name: Package OpenJDK
run: |
zip -r openjdk-build-linux-x86_64-normal-server-release.zip \
- linux-x86_64-normal-server-release/langtools/gensrc \
- linux-x86_64-normal-server-release/corba/gensrc \
- linux-x86_64-normal-server-release/jdk/gensrc \
- linux-x86_64-normal-server-release/jdk/impsrc \
- linux-x86_64-normal-server-release/jdk/lib \
- linux-x86_64-normal-server-release/jdk/classes \
- linux-x86_64-normal-server-release/images
+ linux-x86_64-normal-server-release/*
working-directory: openjdk/build
- name: Upload OpenJDK
uses: actions/upload-artifact@v3
@@ -94,7 +88,7 @@ jobs:
key: jtreg-build--${{ runner.os }}--${{ hashFiles('jtreg/**', 'openjdk/build/linux-x86_64-normal-server-release/images/j2sdk-image', '!jtreg/build') }}-6
- name: Check OpenJDK Test Harness Build Stamp
id: jtreg-build-stamp
- uses: andstor/file-existence-action@v1
+ uses: andstor/file-existence-action@v2
with:
files: jtreg/build/stamp
- name: Build OpenJDK Test Harness
@@ -113,20 +107,30 @@ jobs:
name: Build IKVM
needs:
- build-openjdk
- timeout-minutes: 120
+ timeout-minutes: 240
runs-on: windows-latest
steps:
+ - name: Set Paths (Windows)
+ shell: pwsh
+ run: |
+ $WORKPATH="C:\work"
+ mkdir $WORKPATH
+ mkdir $WORKPATH\temp
+ mkdir $WORKPATH\dotnet
+ mkdir $WORKPATH\nuget
+ mkdir $WORKPATH\nuget\packages
+ mkdir $WORKPATH\ikvm
+ Add-Content $env:GITHUB_ENV "`nWORKPATH=$WORKPATH"
+ Add-Content $env:GITHUB_ENV "`nTMP=$WORKPATH\temp`nTEMP=$WORKPATH\temp`nTMPDIR=$WORKPATH\temp"
+ Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=$WORKPATH\dotnet"
+ Add-Content $env:GITHUB_ENV "`nNUGET_PACKAGES=$WORKPATH\nuget\packages"
+ New-Item -Path "$env:GITHUB_WORKSPACE\work" -ItemType SymbolicLink -Value "$WORKPATH\ikvm"
- name: Checkout Source
uses: actions/checkout@v3
with:
+ path: work
fetch-depth: 0
submodules: recursive
- - name: Move Temporary Directory
- shell: pwsh
- run: Add-Content $env:GITHUB_ENV "`nTMP=${{ runner.temp }}`nTEMP=${{ runner.temp }}`nTMPDIR=${{ runner.temp }}"
- - name: Setup .NET
- shell: pwsh
- run: Add-Content $env:GITHUB_ENV "`nDOTNET_INSTALL_DIR=${{ runner.temp }}\dotnet"
- name: Setup .NET 3.1
uses: actions/setup-dotnet@v3
with:
@@ -139,64 +143,67 @@ jobs:
uses: actions/setup-dotnet@v3
with:
dotnet-version: 7.0.x
+ - name: Cache LLVM and Clang
+ id: cache-llvm
+ uses: actions/cache@v3
+ with:
+ path: C:/Program Files/LLVM
+ key: llvm-15.0
+ - name: Install LLVM and Clang
+ uses: KyleMayes/install-llvm-action@v1
+ with:
+ version: "15.0"
+ cached: ${{ steps.cache-llvm.outputs.cache-hit }}
- name: Install GitVersion
- uses: gittools/actions/gitversion/setup@v0.9.13
+ uses: gittools/actions/gitversion/setup@v0.9.15
with:
versionSpec: 5.x
- name: Execute GitVersion
- uses: gittools/actions/gitversion/execute@v0.9.13
+ uses: gittools/actions/gitversion/execute@v0.9.15
with:
+ targetPath: ${{ env.WORKPATH }}\ikvm
useConfigFile: true
+ configFilePath: ${{ env.WORKPATH }}\ikvm\GitVersion.yml
- name: Setup WSL
- uses: Vampire/setup-wsl@v1
+ uses: Vampire/setup-wsl@v2
with:
additional-packages:
zlib1g-dev
libmpc-dev
libmpfr-dev
libgmp-dev
- clang
- llvm
- llvm-dev
+ libxml2
gcc
g++
- g++-arm-linux-gnueabihf
- gcc-arm-linux-gnueabihf
- libc6-armhf-cross
- gcc-aarch64-linux-gnu
- g++-aarch64-linux-gnu
- libc6-arm64-cross
- - name: Move NuGet Directory
- shell: pwsh
- run: Add-Content $env:GITHUB_ENV "`nNUGET_PACKAGES=${{ runner.temp }}\nuget\packages"
- name: Cache NuGet
uses: actions/cache@v3
with:
- path: ${{ runner.temp }}\nuget\packages
- key: ${{ runner.os }}-nuget-${{ hashFiles('IKVM.sln', 'src\**\*.csproj', 'src\**\*.msbuildproj') }}
- restore-keys: ${{ runner.os }}-nuget-
+ path: ${{ env.NUGET_PACKAGES }}
+ key: ${{ runner.os }}-nuget-2-${{ hashFiles('IKVM.sln', 'src\**\*.csproj', 'src\**\*.msbuildproj') }}
+ restore-keys: ${{ runner.os }}-nuget-2-
- name: Add msbuild to PATH
uses: microsoft/setup-msbuild@v1.1
- name: Download OpenJDK 8 Build
uses: actions/download-artifact@v3
with:
name: openjdk-build-linux-x86_64-normal-server-release
- path: openjdk/build
+ path: ${{ env.WORKPATH }}/ikvm/openjdk/build
- name: Restore OpenJDK 8 Build
shell: pwsh
run: Expand-Archive openjdk-build-linux-x86_64-normal-server-release.zip .
- working-directory: openjdk/build
+ working-directory: ${{ env.WORKPATH }}\ikvm\openjdk\build
- name: Download JTReg Build
uses: actions/download-artifact@v3
with:
name: jtreg-build
- path: jtreg
+ path: ${{ env.WORKPATH }}/ikvm/jtreg
- name: Restore JTReg Build
shell: pwsh
run: Expand-Archive jtreg-build.zip .
- working-directory: jtreg
+ working-directory: ${{ env.WORKPATH }}\ikvm\jtreg
- name: NuGet Restore
run: dotnet restore IKVM.sln
+ working-directory: ${{ env.WORKPATH }}\ikvm
- name: Build Artifacts
run: |
msbuild /m /bl `
@@ -217,15 +224,16 @@ jobs:
/p:CreateHardLinksForPublishFilesIfPossible=true `
/p:ContinuousIntegrationBuild=true `
IKVM.dist.msbuildproj
+ working-directory: ${{ env.WORKPATH }}\ikvm
- name: Upload MSBuild Log
if: ${{ always() }}
uses: actions/upload-artifact@v3
with:
name: msbuild.binlog
- path: msbuild.binlog
+ path: ${{ env.WORKPATH }}\ikvm\msbuild.binlog
- name: Package NuGet Packages
run: tar czvf C:\nuget.tar.gz nuget
- working-directory: dist
+ working-directory: ${{ env.WORKPATH }}\ikvm\dist
- name: Upload NuGet Packages
uses: actions/upload-artifact@v3
with:
@@ -233,7 +241,7 @@ jobs:
path: C:\nuget.tar.gz
- name: Package Binaries
run: tar czvf C:\bin.tar.gz bin
- working-directory: dist
+ working-directory: ${{ env.WORKPATH }}\ikvm\dist
- name: Upload Binaries
uses: actions/upload-artifact@v3
with:
@@ -241,7 +249,7 @@ jobs:
path: C:\bin.tar.gz
- name: Package Tools
run: tar czvf C:\tools.tar.gz tools
- working-directory: dist
+ working-directory: ${{ env.WORKPATH }}\ikvm\dist
- name: Upload Tools
uses: actions/upload-artifact@v3
with:
@@ -249,7 +257,7 @@ jobs:
path: C:\tools.tar.gz
- name: Package Image
run: tar czvf C:\image.tar.gz image
- working-directory: dist
+ working-directory: ${{ env.WORKPATH }}\ikvm\dist
- name: Upload Image
uses: actions/upload-artifact@v3
with:
@@ -257,7 +265,7 @@ jobs:
path: C:\image.tar.gz
- name: Package Tests
run: tar czvf C:\tests.tar.gz tests
- working-directory: dist
+ working-directory: ${{ env.WORKPATH }}\ikvm\dist
- name: Upload Tests
uses: actions/upload-artifact@v3
with:
@@ -267,12 +275,14 @@ jobs:
run: |
git reset --hard
git clean -qfdx
+ working-directory: ${{ env.WORKPATH }}\ikvm
test:
strategy:
matrix:
run:
+ - IKVM.ByteCode.Tests
- IKVM.Tests
- - ikvmc.Tests
+ - IKVM.Tools.Importer.Tests
- IKVM.Tools.Exporter.Tests
- IKVM.Tools.Tests
- IKVM.JTReg.TestAdapter.Tests
@@ -296,7 +306,7 @@ jobs:
sys: linux
- run: IKVM.Tools.Exporter.Tests
tfm: net6.0
- - run: ikvmc.Tests
+ - run: IKVM.Tools.Importer.Tests
tfm: net6.0
- run: IKVM.OpenJDK.Tests?TestPartition=0
tfm: net6.0
@@ -342,7 +352,7 @@ jobs:
- run: IKVM.NET.Sdk.Tests
tfm: net6.0
sys: linux
- name: Test (${{ matrix.run }}:${{ matrix.tfm }}:${{ matrix.sys }}
+ name: Test (${{ matrix.run }}:${{ matrix.tfm }}:${{ matrix.sys }})
needs:
- build-ikvm
timeout-minutes: 240
@@ -352,7 +362,7 @@ jobs:
if: runner.os == 'Windows'
shell: pwsh
run: |
- $dir="C:\run"
+ $dir="C:\work"
mkdir $dir
mkdir $dir\temp
mkdir $dir\dotnet
@@ -368,7 +378,7 @@ jobs:
if: runner.os == 'Linux'
shell: pwsh
run: |
- $dir="${{ runner.temp }}/run"
+ $dir="${{ runner.temp }}/work"
mkdir $dir
mkdir $dir/temp
mkdir $dir/dotnet
@@ -394,7 +404,7 @@ jobs:
dotnet-version: 7.0.x
- name: Setup WSL
if: matrix.sys == 'windows'
- uses: Vampire/setup-wsl@v1
+ uses: Vampire/setup-wsl@v2
- name: Download Tests
uses: actions/download-artifact@v3
with:
@@ -464,12 +474,12 @@ jobs:
with:
dotnet-version: 7.0.x
- name: Install GitVersion
- uses: gittools/actions/gitversion/setup@v0.9.13
+ uses: gittools/actions/gitversion/setup@v0.9.15
with:
versionSpec: 5.x
- name: Execute GitVersion
id: GitVersion
- uses: gittools/actions/gitversion/execute@v0.9.13
+ uses: gittools/actions/gitversion/execute@v0.9.15
with:
useConfigFile: true
- name: Download NuGet Packages
diff --git a/.gitmodules b/.gitmodules
index 6a79c78f6a..9bdb8ca246 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,8 +1,8 @@
[submodule "openjdk"]
path = openjdk
- url = https://github.com/ikvm-revived/jdk8u.git
+ url = https://github.com/ikvmnet/jdk8u.git
ignore = dirty
[submodule "jtreg"]
path = jtreg
- url = https://github.com/ikvm-revived/jtreg.git
+ url = https://github.com/ikvmnet/jtreg.git
ignore = dirty
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 2306698d42..0dbe637aea 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,13 +2,24 @@
## Build
-Project should open within Visual Studio. Project can also be built with MSBuild on a Windows host. Project cannot currently be built on a Linux host, nor with an exclusively .NET Core version of MSBuild.
+The project can be opened in Visual Studio, or it can be be built with MSBuild on a Windows host. The project cannot currently be built on a Linux host, nor with an exclusively .NET Core version of MSBuild.
-The OpenJDK JDK8u source hierarchy and build results for Linux/x64 is required. These build artifacts cannot be built on Windows, or modern Linux hosts. Instead, they must be built on a host with GCC 4.3 available. Debian Lenny is known to work acceptably. The CI/CD GitHub action can serve as a demonstration of this.
+Prerequisites for building the project:
+* A clone of the IKVM repository which includes submodules (e.g. `git clone --recurse-submodules https://github.com/ikvm-revived/ikvm.git`)
+* Visual Studio configured for: .NET desktop development, Desktop development with C++ (including C++ Clang tools for Windows), Linux and Embedded Development with C++
+* A JDK 8 installation. The `JAVA_HOME` environment variable needs to be point to the JDK 8 directory, or the version of `javac` available on the path needs to be from JDK 8 (You can download a suitable JDK from [Adoptium](https://adoptium.net/))
+* The OpenJDK JDK8u build result for Linux/x64. This build artifact cannot be built on Windows, or modern Linux hosts. Instead, it must be built on a host with GCC 4.3 available. (Debian Lenny is known to work acceptably, the CI/CD GitHub action can serve as a demonstration of this). The easiest way to acquire this artifact without building it yourself is to use one generated by the IKVM respository's [GitHub Actions](https://github.com/ikvm-revived/ikvm/actions). Click on a build result for a relevant branch (typically you'll want to use `develop`) and scroll down to the bottom of the list to locate `openjdk-build-linux-x86_64-normal-server-release`. Download this zip file from this link and unzip into
+ `openjdk/build`.
+ * LLVM installed with `clang` available on the path. LLVM is shipped with Visual Studio, but you will need to update your PATH to include the relevant bin directory (typically: `C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\Llvm\bin`) or you can install a [standalone distribution of LLVM](https://releases.llvm.org/).
+ * Windows Subsystem for Linux enabled, and a Linux distribution with g++ installed
+ * 200GB of free disk space
+
+ Once the prerequisites are in place, to build IKVM from the command line you will use the following commands:
-The GitHub action's generated artifact can simply be extract into the appropriate spot in `openjdk/build` to avoid building it yourself. Navigate to the GitHub Actions, find the latest successfuly build for the branch you're concerned with, and download the `openjdk-build-linux-x86_64-normal-server-release` artifact. Extract this zip file into `openjdk/build`.
-
-IKVM includes a native library named 'ikvm-native' which must be built for the JNI functionality to work. The solution includes `.vcxproj` files that build both the win-x64, linux-x86 and linux-x64 versions of these libraries. However, the linux-* version require WSL to be enabled on your development machine. Within this WSL distribution ensure you have installed the GCC toolset. For Debian based distributions, this should be as simple as typing `apt-get install g++`.
+ ```
+ dotnet restore IKVM.sln
+ msbuild IKVM.dist.msbuildproj
+ ```
## Project
diff --git a/Directory.Build.props b/Directory.Build.props
index 519fe0b748..5469fd0dd1 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,9 +4,11 @@
$(NoWarn);1591;1573;CS8002;NU5100;NU5118;NU5128
true
true
- 15
- 15000
false
+ 30
+ 15000
+ win7-x64;win7-x86;win81-arm;linux-x64;linux-arm;linux-arm64;osx-x64
+ win7-x64;win7-x86;win81-arm;linux-x64;linux-arm;linux-arm64;osx-x64
@@ -32,9 +34,9 @@
- https://github.com/ikvm-revived/ikvm.git
+ https://github.com/ikvmnet/ikvm.git
git
- https://github.com/ikvm-revived/ikvm
+ https://github.com/ikvmnet/ikvm
$(Version)
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 7b0e9df682..58a5d0c409 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -1,7 +1,6 @@
-
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/FUNDING.yml b/FUNDING.yml
index 037ca9e474..f9fe3fdd8a 100644
--- a/FUNDING.yml
+++ b/FUNDING.yml
@@ -1,2 +1,2 @@
-github: [wasabii, NightOwl888, joniles]
+github: [wasabii, NightOwl888]
custom: ["https://www.paypal.me/ShadStorhaug"]
diff --git a/IKVM.deps.props b/IKVM.deps.props
index 58ddf05804..cc8c723926 100644
--- a/IKVM.deps.props
+++ b/IKVM.deps.props
@@ -3,7 +3,6 @@
-
@@ -11,6 +10,7 @@
+
@@ -28,6 +28,7 @@
+
diff --git a/IKVM.dist.msbuildproj b/IKVM.dist.msbuildproj
index 2e8612da99..8a372581d2 100644
--- a/IKVM.dist.msbuildproj
+++ b/IKVM.dist.msbuildproj
@@ -10,23 +10,23 @@
- dist\dist-bin:Publish
+ dist%5Cdist-bin:Publish
PublishDir=$(DistDir)\bin
- dist\dist-tools:Publish
+ dist%5Cdist-tools:Publish
PublishDir=$(DistDir)\tools
- dist\dist-image:Publish
+ dist%5Cdist-image:Publish
PublishDir=$(DistDir)\image
- dist\dist-nuget:Publish
+ dist%5Cdist-nuget:Publish
PublishDir=$(DistDir)\nuget
- dist\dist-tests:Publish
+ dist%5Cdist-tests:Publish
PublishDir=$(DistDir)\tests
diff --git a/IKVM.refs.props b/IKVM.refs.props
index eb72955dfc..51ed217988 100644
--- a/IKVM.refs.props
+++ b/IKVM.refs.props
@@ -1,6 +1,10 @@
+
+ true
+ all
+
true
all
@@ -11,64 +15,4 @@
-
-
- Platform=x64
- runtimes\win-x64\native
-
-
- Platform=Win32
- runtimes\win-x86\native
-
-
- Platform=ARM
- runtimes\win-arm\native
-
-
- Platform=x64
- runtimes\linux-x64\native
-
-
- Platform=x86
- runtimes\linux-arm\native
-
-
- Platform=x64
- runtimes\linux-arm64\native
-
-
-
-
-
- Platform=x64
- runtimes\win-x64\native
- false
-
-
- Platform=Win32
- runtimes\win-x86\native
- false
-
-
- Platform=ARM
- runtimes\win-arm\native
- false
-
-
- Platform=x64
- runtimes\linux-x64\native
- false
-
-
- Platform=x86
- runtimes\linux-arm\native
- false
-
-
- Platform=x64
- runtimes\linux-arm64\native
- false
-
-
-
diff --git a/IKVM.sln b/IKVM.sln
index 764818466f..58acd14991 100644
--- a/IKVM.sln
+++ b/IKVM.sln
@@ -39,20 +39,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tests", "src\IKVM.Test
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Runner", "src\IKVM.Tools.Runner\IKVM.Tools.Runner.csproj", "{8267A3D1-2B36-471C-9E9F-2E1B0C223632}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ikvm-native", "ikvm-native", "{0606E530-F834-44A3-9B4E-37852A06D11B}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-native-win", "src\ikvm-native\ikvm-native-win.vcxproj", "{D8B580D1-D12B-39CD-A42B-BAEE36602AA1}"
-EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-native-linux", "src\ikvm-native\ikvm-native-linux.vcxproj", "{F2A81A31-C65F-4B4E-A6FE-B14E71515EA3}"
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-tests-native-win", "src\ikvm-tests-native\ikvm-tests-native-win.vcxproj", "{A275C11D-F06E-475F-AEBA-7F7A1EF8D140}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-tests-native-linux", "src\ikvm-tests-native\ikvm-tests-native-linux.vcxproj", "{E566FE94-C551-43A7-843C-5E8199CA7373}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ikvm-tests-native", "ikvm-tests-native", "{B6083619-C4E8-4D8E-95BD-217E3663134C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ikvmc.Tests", "src\ikvmc.Tests\ikvmc.Tests.csproj", "{CF80B061-AF1B-4C15-A725-10C0A5D267CC}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tests.Util", "src\IKVM.Tests.Util\IKVM.Tests.Util.csproj", "{E2C71F2E-2940-4990-A618-E7E47C00D6A6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Util", "src\IKVM.Util\IKVM.Util.csproj", "{ADACA319-6E64-4351-8E00-FB03B98BFC8C}"
@@ -75,9 +67,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.MSBuild.Tests", "src\I
{95C8865C-AB87-4998-880E-38B7F76BAC57} = {95C8865C-AB87-4998-880E-38B7F76BAC57}
{95F23908-2D65-40C7-9EF1-B5C8543503AA} = {95F23908-2D65-40C7-9EF1-B5C8543503AA}
{9AE06A6D-1335-482C-8ABF-2FE1BADF5AD7} = {9AE06A6D-1335-482C-8ABF-2FE1BADF5AD7}
- {A204C3A9-F559-4E50-B667-F8B5D27501D1} = {A204C3A9-F559-4E50-B667-F8B5D27501D1}
{AE5BFBC6-7DD4-4ACA-BEA7-86F842F38B64} = {AE5BFBC6-7DD4-4ACA-BEA7-86F842F38B64}
- {B02CE9AA-AB52-491A-A089-A1BDF297A800} = {B02CE9AA-AB52-491A-A089-A1BDF297A800}
{BE31AC7C-79CC-4180-A5E8-BEB3FD7579DC} = {BE31AC7C-79CC-4180-A5E8-BEB3FD7579DC}
{CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D} = {CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D}
{DF09A175-C790-434D-9810-A6BBB3EEC548} = {DF09A175-C790-434D-9810-A6BBB3EEC548}
@@ -93,7 +83,9 @@ EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Java", "src\IKVM.Java\IKVM.Java.msbuildproj", "{ABD81C7E-F986-4018-986E-ACAF82C64D3A}"
ProjectSection(ProjectDependencies) = postProject
{0A87FF49-AC9D-479A-ACBB-F4028A0BE1F7} = {0A87FF49-AC9D-479A-ACBB-F4028A0BE1F7}
+ {17979A73-C0CE-41CD-B54F-4E72B998E6D0} = {17979A73-C0CE-41CD-B54F-4E72B998E6D0}
{2F29E48C-63C3-4E47-BCBA-A7454B9119CF} = {2F29E48C-63C3-4E47-BCBA-A7454B9119CF}
+ {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7} = {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.NET.Sdk", "src\IKVM.NET.Sdk\IKVM.NET.Sdk.msbuildproj", "{704BD7C7-7746-4D72-A86E-ECFE7BBD80CC}"
@@ -111,7 +103,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Exporter", "src\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Exporter.Tests", "src\IKVM.Tools.Exporter.Tests\IKVM.Tools.Exporter.Tests.csproj", "{EFF93C65-3F45-48B6-9178-1E68ADD41974}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Tests", "src\IKVM.Tools.Tests\IKVM.Tools.Tests.csproj", "{E4E6EAB9-2C68-49AC-A212-D464F1BB483B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Tests", "src\IKVM.Tools.Tests\IKVM.Tools.Tests.csproj", "{8C95ECC6-E996-4DA0-A4C8-F05373CBB0EA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "javac", "src\javac\javac.msbuildproj", "{78BE5834-EC02-48C5-A83A-2693D777375A}"
EndProject
@@ -129,6 +121,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "rmic", "src\rmic\rmic.msbui
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jar", "src\jar\jar.msbuildproj", "{B240F8BA-6C35-4C19-9035-2821CC6BFEA8}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "orbd", "src\orbd\orbd.msbuildproj", "{B240F8BA-6C35-4C19-9035-2821CC6BFFA8}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jarsigner", "src\jarsigner\jarsigner.msbuildproj", "{C6C34134-D80C-43CC-A477-32B4172374D2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "javadoc", "src\javadoc\javadoc.msbuildproj", "{4371190E-AD8B-4625-A520-B745B05DE005}"
@@ -141,6 +135,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "javah", "src\javah\javah.ms
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "javap", "src\javap\javap.msbuildproj", "{03F00630-3625-440D-BE43-51751FCE7806}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "schemagen", "src\schemagen\schemagen.msbuildproj", "{03F00630-3625-440D-BE43-51751FCE7802}"
+EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "jdeps", "src\jdeps\jdeps.msbuildproj", "{FB1F02F2-3A18-49CC-A288-EC0834D90EED}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "wsgen", "src\wsgen\wsgen.msbuildproj", "{ECF6B110-DF3B-4D29-91C8-45AD4C7D4B17}"
@@ -169,10 +165,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK.runtime.win7
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image", "src\IKVM.Image\IKVM.Image.csproj", "{95C8865C-AB87-4998-880E-38B7F76BAC57}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK", "src\IKVM.Image.JDK\IKVM.Image.JDK.csproj", "{B02CE9AA-AB52-491A-A089-A1BDF297A800}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE", "src\IKVM.Image.JRE\IKVM.Image.JRE.csproj", "{A204C3A9-F559-4E50-B667-F8B5D27501D1}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE.runtime.win7-x86", "src\IKVM.Image.JRE.runtime.win7-x86\IKVM.Image.JRE.runtime.win7-x86.csproj", "{CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D}"
ProjectSection(ProjectDependencies) = postProject
{162A1F6D-7752-4686-A6C2-923633F4E4C0} = {162A1F6D-7752-4686-A6C2-923633F4E4C0}
@@ -219,6 +211,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE.runtime.win8
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.JTReg.TestAdapter.Core", "src\IKVM.JTReg.TestAdapter.Core\IKVM.JTReg.TestAdapter.Core.csproj", "{DD5AEB88-DC55-43C8-81C1-2879F73C8DE0}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK.runtime.osx-x64", "src\IKVM.Image.JDK.runtime.osx-x64\IKVM.Image.JDK.runtime.osx-x64.csproj", "{011FF139-A9E6-4F10-BBC8-28BC1A38D966}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE.runtime.osx-x64", "src\IKVM.Image.JRE.runtime.osx-x64\IKVM.Image.JRE.runtime.osx-x64.csproj", "{FD39DA19-C81E-4752-B308-43594CC201BB}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.ByteCode", "src\IKVM.ByteCode\IKVM.ByteCode.csproj", "{032B77B4-199E-4077-841E-951BAD862050}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dist", "dist", "{F8B6B3D8-006C-4D8F-8883-EB48D9EB417F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dist-bin", "src\dist-bin\dist-bin.csproj", "{B60EC8AA-3CDF-45E4-9ACD-3246B375C027}"
@@ -240,12 +238,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.MSBuild.Tools.runtime.
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.MSBuild.Tools.runtime.linux-x64", "src\IKVM.MSBuild.Tools.runtime.linux-x64\IKVM.MSBuild.Tools.runtime.linux-x64.csproj", "{F162126C-DA9C-4195-B295-CF1020DBA98A}"
- ProjectSection(ProjectDependencies) = postProject
- {0A87FF49-AC9D-479A-ACBB-F4028A0BE1F7} = {0A87FF49-AC9D-479A-ACBB-F4028A0BE1F7}
- {2F29E48C-63C3-4E47-BCBA-A7454B9119CF} = {2F29E48C-63C3-4E47-BCBA-A7454B9119CF}
- EndProjectSection
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-native-linux-arm64", "src\ikvm-native\ikvm-native-linux-arm64.vcxproj", "{BE117661-1954-42C3-8A16-3F89815F7222}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.MSBuild.Tools.runtime.osx-x64", "src\IKVM.MSBuild.Tools.runtime.osx-x64\IKVM.MSBuild.Tools.runtime.osx-x64.csproj", "{E690EC25-3A73-408F-93B8-36990B2A9D0E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK.runtime.linux-arm64", "src\IKVM.Image.JDK.runtime.linux-arm64\IKVM.Image.JDK.runtime.linux-arm64.csproj", "{DF09A175-C790-434D-9810-A6BBB3EEC548}"
ProjectSection(ProjectDependencies) = postProject
@@ -269,8 +263,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.MSBuild.Tools.runtime.
{2F29E48C-63C3-4E47-BCBA-A7454B9119CF} = {2F29E48C-63C3-4E47-BCBA-A7454B9119CF}
EndProjectSection
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ikvm-native-linux-arm", "src\ikvm-native\ikvm-native-linux-arm.vcxproj", "{8A321DFF-99AB-4279-828A-A9BA872010FD}"
-EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK.runtime.linux-arm", "src\IKVM.Image.JDK.runtime.linux-arm\IKVM.Image.JDK.runtime.linux-arm.csproj", "{437506E7-B124-4808-8AE7-8864AC9EA36A}"
ProjectSection(ProjectDependencies) = postProject
{CCE3DE31-5DE7-4157-90D1-BAFA08C181EC} = {CCE3DE31-5DE7-4157-90D1-BAFA08C181EC}
@@ -283,6 +275,18 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE.runtime.linu
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "native2ascii", "src\native2ascii\native2ascii.msbuildproj", "{7BC3FC79-5938-4CD7-9528-47D5DC18729E}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JDK", "src\IKVM.Image.JDK\IKVM.Image.JDK.csproj", "{7C97E1C8-7FEB-4C2A-A149-7D77E15838BF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Image.JRE", "src\IKVM.Image.JRE\IKVM.Image.JRE.csproj", "{819C752E-1F74-4447-9214-86EF337A1DEF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Importer", "src\IKVM.Tools.Importer\IKVM.Tools.Importer.csproj", "{50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tools.Importer.Tests", "src\IKVM.Tools.Importer.Tests\IKVM.Tools.Importer.Tests.csproj", "{6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.Tests.Java", "src\IKVM.Tests.Java\IKVM.Tests.Java.msbuildproj", "{87CB99AE-16D1-4D58-BEAC-72485E02B321}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IKVM.ByteCode.Tests", "src\IKVM.ByteCode.Tests\IKVM.ByteCode.Tests.csproj", "{60E804E4-B25B-4C9A-A3E6-F83D618CFD26}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -321,14 +325,6 @@ Global
{8267A3D1-2B36-471C-9E9F-2E1B0C223632}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8267A3D1-2B36-471C-9E9F-2E1B0C223632}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8267A3D1-2B36-471C-9E9F-2E1B0C223632}.Release|Any CPU.Build.0 = Release|Any CPU
- {D8B580D1-D12B-39CD-A42B-BAEE36602AA1}.Debug|Any CPU.ActiveCfg = Debug|x64
- {D8B580D1-D12B-39CD-A42B-BAEE36602AA1}.Debug|Any CPU.Build.0 = Debug|x64
- {D8B580D1-D12B-39CD-A42B-BAEE36602AA1}.Release|Any CPU.ActiveCfg = Release|x64
- {D8B580D1-D12B-39CD-A42B-BAEE36602AA1}.Release|Any CPU.Build.0 = Release|x64
- {F2A81A31-C65F-4B4E-A6FE-B14E71515EA3}.Debug|Any CPU.ActiveCfg = Debug|x64
- {F2A81A31-C65F-4B4E-A6FE-B14E71515EA3}.Debug|Any CPU.Build.0 = Debug|x64
- {F2A81A31-C65F-4B4E-A6FE-B14E71515EA3}.Release|Any CPU.ActiveCfg = Release|x64
- {F2A81A31-C65F-4B4E-A6FE-B14E71515EA3}.Release|Any CPU.Build.0 = Release|x64
{A275C11D-F06E-475F-AEBA-7F7A1EF8D140}.Debug|Any CPU.ActiveCfg = Debug|x64
{A275C11D-F06E-475F-AEBA-7F7A1EF8D140}.Debug|Any CPU.Build.0 = Debug|x64
{A275C11D-F06E-475F-AEBA-7F7A1EF8D140}.Release|Any CPU.ActiveCfg = Release|x64
@@ -337,10 +333,6 @@ Global
{E566FE94-C551-43A7-843C-5E8199CA7373}.Debug|Any CPU.Build.0 = Debug|x64
{E566FE94-C551-43A7-843C-5E8199CA7373}.Release|Any CPU.ActiveCfg = Release|x64
{E566FE94-C551-43A7-843C-5E8199CA7373}.Release|Any CPU.Build.0 = Release|x64
- {CF80B061-AF1B-4C15-A725-10C0A5D267CC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {CF80B061-AF1B-4C15-A725-10C0A5D267CC}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {CF80B061-AF1B-4C15-A725-10C0A5D267CC}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {CF80B061-AF1B-4C15-A725-10C0A5D267CC}.Release|Any CPU.Build.0 = Release|Any CPU
{E2C71F2E-2940-4990-A618-E7E47C00D6A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E2C71F2E-2940-4990-A618-E7E47C00D6A6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2C71F2E-2940-4990-A618-E7E47C00D6A6}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -389,10 +381,10 @@ Global
{EFF93C65-3F45-48B6-9178-1E68ADD41974}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EFF93C65-3F45-48B6-9178-1E68ADD41974}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EFF93C65-3F45-48B6-9178-1E68ADD41974}.Release|Any CPU.Build.0 = Release|Any CPU
- {E4E6EAB9-2C68-49AC-A212-D464F1BB483B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {E4E6EAB9-2C68-49AC-A212-D464F1BB483B}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {E4E6EAB9-2C68-49AC-A212-D464F1BB483B}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {E4E6EAB9-2C68-49AC-A212-D464F1BB483B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8C95ECC6-E996-4DA0-A4C8-F05373CBB0EA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8C95ECC6-E996-4DA0-A4C8-F05373CBB0EA}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8C95ECC6-E996-4DA0-A4C8-F05373CBB0EA}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8C95ECC6-E996-4DA0-A4C8-F05373CBB0EA}.Release|Any CPU.Build.0 = Release|Any CPU
{78BE5834-EC02-48C5-A83A-2693D777375A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{78BE5834-EC02-48C5-A83A-2693D777375A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{78BE5834-EC02-48C5-A83A-2693D777375A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -425,6 +417,10 @@ Global
{B240F8BA-6C35-4C19-9035-2821CC6BFEA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B240F8BA-6C35-4C19-9035-2821CC6BFEA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B240F8BA-6C35-4C19-9035-2821CC6BFEA8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B240F8BA-6C35-4C19-9035-2821CC6BFFA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B240F8BA-6C35-4C19-9035-2821CC6BFFA8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B240F8BA-6C35-4C19-9035-2821CC6BFFA8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B240F8BA-6C35-4C19-9035-2821CC6BFFA8}.Release|Any CPU.Build.0 = Release|Any CPU
{C6C34134-D80C-43CC-A477-32B4172374D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6C34134-D80C-43CC-A477-32B4172374D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6C34134-D80C-43CC-A477-32B4172374D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -449,6 +445,10 @@ Global
{03F00630-3625-440D-BE43-51751FCE7806}.Debug|Any CPU.Build.0 = Debug|Any CPU
{03F00630-3625-440D-BE43-51751FCE7806}.Release|Any CPU.ActiveCfg = Release|Any CPU
{03F00630-3625-440D-BE43-51751FCE7806}.Release|Any CPU.Build.0 = Release|Any CPU
+ {03F00630-3625-440D-BE43-51751FCE7802}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {03F00630-3625-440D-BE43-51751FCE7802}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {03F00630-3625-440D-BE43-51751FCE7802}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {03F00630-3625-440D-BE43-51751FCE7802}.Release|Any CPU.Build.0 = Release|Any CPU
{FB1F02F2-3A18-49CC-A288-EC0834D90EED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{FB1F02F2-3A18-49CC-A288-EC0834D90EED}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FB1F02F2-3A18-49CC-A288-EC0834D90EED}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -481,14 +481,6 @@ Global
{95C8865C-AB87-4998-880E-38B7F76BAC57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95C8865C-AB87-4998-880E-38B7F76BAC57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95C8865C-AB87-4998-880E-38B7F76BAC57}.Release|Any CPU.Build.0 = Release|Any CPU
- {B02CE9AA-AB52-491A-A089-A1BDF297A800}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {B02CE9AA-AB52-491A-A089-A1BDF297A800}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {B02CE9AA-AB52-491A-A089-A1BDF297A800}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {B02CE9AA-AB52-491A-A089-A1BDF297A800}.Release|Any CPU.Build.0 = Release|Any CPU
- {A204C3A9-F559-4E50-B667-F8B5D27501D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A204C3A9-F559-4E50-B667-F8B5D27501D1}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A204C3A9-F559-4E50-B667-F8B5D27501D1}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A204C3A9-F559-4E50-B667-F8B5D27501D1}.Release|Any CPU.Build.0 = Release|Any CPU
{CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CBB5E3B3-2B0F-41F3-8110-FDD9614FCE5D}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -521,6 +513,18 @@ Global
{DD5AEB88-DC55-43C8-81C1-2879F73C8DE0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DD5AEB88-DC55-43C8-81C1-2879F73C8DE0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DD5AEB88-DC55-43C8-81C1-2879F73C8DE0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {011FF139-A9E6-4F10-BBC8-28BC1A38D966}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {011FF139-A9E6-4F10-BBC8-28BC1A38D966}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {011FF139-A9E6-4F10-BBC8-28BC1A38D966}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {011FF139-A9E6-4F10-BBC8-28BC1A38D966}.Release|Any CPU.Build.0 = Release|Any CPU
+ {FD39DA19-C81E-4752-B308-43594CC201BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {FD39DA19-C81E-4752-B308-43594CC201BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {FD39DA19-C81E-4752-B308-43594CC201BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {FD39DA19-C81E-4752-B308-43594CC201BB}.Release|Any CPU.Build.0 = Release|Any CPU
+ {032B77B4-199E-4077-841E-951BAD862050}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {032B77B4-199E-4077-841E-951BAD862050}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {032B77B4-199E-4077-841E-951BAD862050}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {032B77B4-199E-4077-841E-951BAD862050}.Release|Any CPU.Build.0 = Release|Any CPU
{B60EC8AA-3CDF-45E4-9ACD-3246B375C027}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B60EC8AA-3CDF-45E4-9ACD-3246B375C027}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B60EC8AA-3CDF-45E4-9ACD-3246B375C027}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -553,12 +557,10 @@ Global
{F162126C-DA9C-4195-B295-CF1020DBA98A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F162126C-DA9C-4195-B295-CF1020DBA98A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F162126C-DA9C-4195-B295-CF1020DBA98A}.Release|Any CPU.Build.0 = Release|Any CPU
- {BE117661-1954-42C3-8A16-3F89815F7222}.Debug|Any CPU.ActiveCfg = Debug|x64
- {BE117661-1954-42C3-8A16-3F89815F7222}.Debug|Any CPU.Build.0 = Debug|x64
- {BE117661-1954-42C3-8A16-3F89815F7222}.Debug|Any CPU.Deploy.0 = Debug|x64
- {BE117661-1954-42C3-8A16-3F89815F7222}.Release|Any CPU.ActiveCfg = Release|x64
- {BE117661-1954-42C3-8A16-3F89815F7222}.Release|Any CPU.Build.0 = Release|x64
- {BE117661-1954-42C3-8A16-3F89815F7222}.Release|Any CPU.Deploy.0 = Release|x64
+ {E690EC25-3A73-408F-93B8-36990B2A9D0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {E690EC25-3A73-408F-93B8-36990B2A9D0E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {E690EC25-3A73-408F-93B8-36990B2A9D0E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {E690EC25-3A73-408F-93B8-36990B2A9D0E}.Release|Any CPU.Build.0 = Release|Any CPU
{DF09A175-C790-434D-9810-A6BBB3EEC548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF09A175-C790-434D-9810-A6BBB3EEC548}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF09A175-C790-434D-9810-A6BBB3EEC548}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -575,12 +577,6 @@ Global
{9AE06A6D-1335-482C-8ABF-2FE1BADF5AD7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9AE06A6D-1335-482C-8ABF-2FE1BADF5AD7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9AE06A6D-1335-482C-8ABF-2FE1BADF5AD7}.Release|Any CPU.Build.0 = Release|Any CPU
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Debug|Any CPU.ActiveCfg = Debug|x86
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Debug|Any CPU.Build.0 = Debug|x86
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Debug|Any CPU.Deploy.0 = Debug|x86
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Release|Any CPU.ActiveCfg = Release|x86
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Release|Any CPU.Build.0 = Release|x86
- {8A321DFF-99AB-4279-828A-A9BA872010FD}.Release|Any CPU.Deploy.0 = Release|x86
{437506E7-B124-4808-8AE7-8864AC9EA36A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{437506E7-B124-4808-8AE7-8864AC9EA36A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{437506E7-B124-4808-8AE7-8864AC9EA36A}.Release|Any CPU.ActiveCfg = Release|Any CPU
@@ -593,13 +589,35 @@ Global
{7BC3FC79-5938-4CD7-9528-47D5DC18729E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7BC3FC79-5938-4CD7-9528-47D5DC18729E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7BC3FC79-5938-4CD7-9528-47D5DC18729E}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7C97E1C8-7FEB-4C2A-A149-7D77E15838BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7C97E1C8-7FEB-4C2A-A149-7D77E15838BF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7C97E1C8-7FEB-4C2A-A149-7D77E15838BF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7C97E1C8-7FEB-4C2A-A149-7D77E15838BF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {819C752E-1F74-4447-9214-86EF337A1DEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {819C752E-1F74-4447-9214-86EF337A1DEF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {819C752E-1F74-4447-9214-86EF337A1DEF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {819C752E-1F74-4447-9214-86EF337A1DEF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {50954AE0-E513-4CE7-AC8E-F3896CA0BEB7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6C942D7C-654A-4CBE-B3A9-887A37D2FA4C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {87CB99AE-16D1-4D58-BEAC-72485E02B321}.Release|Any CPU.Build.0 = Release|Any CPU
+ {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {60E804E4-B25B-4C9A-A3E6-F83D618CFD26}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {D8B580D1-D12B-39CD-A42B-BAEE36602AA1} = {0606E530-F834-44A3-9B4E-37852A06D11B}
- {F2A81A31-C65F-4B4E-A6FE-B14E71515EA3} = {0606E530-F834-44A3-9B4E-37852A06D11B}
{A275C11D-F06E-475F-AEBA-7F7A1EF8D140} = {B6083619-C4E8-4D8E-95BD-217E3663134C}
{E566FE94-C551-43A7-843C-5E8199CA7373} = {B6083619-C4E8-4D8E-95BD-217E3663134C}
{B60EC8AA-3CDF-45E4-9ACD-3246B375C027} = {F8B6B3D8-006C-4D8F-8883-EB48D9EB417F}
@@ -607,8 +625,6 @@ Global
{6C106109-0131-4413-8D41-348256EADC7E} = {F8B6B3D8-006C-4D8F-8883-EB48D9EB417F}
{5BBD66DA-83E0-4FCA-967E-75A87C21E89A} = {F8B6B3D8-006C-4D8F-8883-EB48D9EB417F}
{04344989-84DC-4D21-BC9F-C4A23D251568} = {F8B6B3D8-006C-4D8F-8883-EB48D9EB417F}
- {BE117661-1954-42C3-8A16-3F89815F7222} = {0606E530-F834-44A3-9B4E-37852A06D11B}
- {8A321DFF-99AB-4279-828A-A9BA872010FD} = {0606E530-F834-44A3-9B4E-37852A06D11B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A03ACB78-6C3F-4E09-BAF1-16E41D5A3945}
diff --git a/README.md b/README.md
index c53413e93b..a9a2156703 100644
--- a/README.md
+++ b/README.md
@@ -40,8 +40,8 @@ These tasks can be done **without porting source code** to .NET.
- .NET Core 3.1 and higher
- .NET 5 and higher
- Java SE 8
-- Windows x86/x64
-- Linux x64
+- Windows x86/x64/ARM/ARM64
+- Linux x64/ARM/ARM64
## Documentation
@@ -63,7 +63,7 @@ PM> Install-Package IKVM.Maven.Sdk
### Tools
-The tools are available for download on the [Releases](https://github.com/ikvm-revived/ikvm/releases) page.
+The tools are available for download on the [Releases](https://github.com/ikvmnet/ikvm/releases) page.
### Runtime Images
@@ -74,7 +74,7 @@ PM> Install-Package IKVM.Image.JRE
PM> Install-Package IKVM.Image.JDK
```
-A standalone JDK distributable is available for download on the [Releases](https://github.com/ikvm-revived/ikvm/releases) page. This directory structure should suffice as a `JAVA_HOME` path for standard Java applications.
+A standalone JDK distributable is available for download on the [Releases](https://github.com/ikvmnet/ikvm/releases) page. This directory structure should suffice as a `JAVA_HOME` path for standard Java applications.
## Usage
@@ -174,4 +174,14 @@ The `Automatic-Module-Name` is either a specified attribute of the JAR manifest,
### MavenReference
-See the [ikvm-maven Readme](https://github.com/ikvm-revived/ikvm-maven#readme) for usage instructions.
+See the [ikvm-maven Readme](https://github.com/ikvmnet/ikvm-maven#readme) for usage instructions.
+
+### Notice To Project Owners
+
+The IKVM project recommends that people do not redistribute FOSS Java libraries compiled with IKVM over public systems such as NuGet.org, unless you are the original owner of that software and have a compelling reason.
+
+Creating copies of FOSS Java libraries and publishing them to distribution mechanisms such as NuGet.org creates eco-system confusion and dependency conflicts downstream. We provide a system so that .NET users of Java libraries can reference those libraries directly out of the standard Java ecosystem mechanisms: Maven Central, etc though IKVM.Maven. Remember, very few libraries exist in a vacuum. Libraries often depend on dozens of other libraries. Two unrelated Java libraries often depend on the same underlying Java library. A complex method of dependency conflict resolution and version unification has to be involved in resolving this hierarchy for any individual downstream project. You are likely going to be introducing duplicate classes into the users of your versions, or causing your users to depend upon the wrong version of other libraries.
+
+There are exceptions to this advice, such as the library not being published to Maven. In that case, guidance would be to advocate that the original Java library in fact be published to Maven, or do the work yourself, as that is the appropriate place for Java libraries.
+
+We cannot force you to conform to this advice. But for the health of the ecosystem, we urge you to take it under consideration yourself.
diff --git a/ext/helloworld-mod.jar b/ext/helloworld-mod.jar
new file mode 100644
index 0000000000..95a213d79e
Binary files /dev/null and b/ext/helloworld-mod.jar differ
diff --git a/openjdk b/openjdk
index 83077ab59f..297326379a 160000
--- a/openjdk
+++ b/openjdk
@@ -1 +1 @@
-Subproject commit 83077ab59f33101225d769d03209063d54cce371
+Subproject commit 297326379ab44b57d59e25ed3fbb56b22cdc53e5
diff --git a/openjdk.props b/openjdk.props
index b837105021..edb95f3064 100644
--- a/openjdk.props
+++ b/openjdk.props
@@ -1,8 +1,8 @@
- $(MSBuildThisFileDirectory)openjdk
- OpenJDK 8u45 b37
- 1.8.0_45-b37
+ $(MSBuildThisFileDirectory)openjdk\
+ OpenJDK 8u60 b32
+ 1.8.0_60-b32
Oracle
1.8.0
Oracle Corporation
@@ -17,416 +17,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -504,13 +95,7 @@
-
-
-
-
-
-
-
+
@@ -616,92 +201,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -727,142 +227,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -2275,6007 +1640,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -9737,902 +3107,48 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -10654,16 +3170,7 @@
-
-
-
-
-
-
-
-
-
-
+
@@ -10750,49 +3257,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -10810,16 +3275,7 @@
-
-
-
-
-
-
-
-
-
-
+
@@ -10829,25 +3285,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -10863,17 +3301,7 @@
-
-
-
-
-
-
-
-
-
-
-
+
@@ -10882,12 +3310,7 @@
-
-
-
-
-
-
+
@@ -10897,52 +3320,12 @@
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -10958,1698 +3341,18 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
@@ -13029,6 +3732,7 @@
+
@@ -13164,119 +3868,33 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -13462,558 +4080,14 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
@@ -14612,9 +4686,7 @@
-
-
-
+
@@ -14622,24 +4694,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -14778,502 +4839,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
@@ -15355,522 +4921,12 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
@@ -15935,6 +4991,7 @@
+
diff --git a/src/IKVM.ByteCode.Tests/0.class b/src/IKVM.ByteCode.Tests/0.class
new file mode 100644
index 0000000000..b5b6c8f2b5
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/0.class differ
diff --git a/src/IKVM.ByteCode.Tests/ClassReaderTests.cs b/src/IKVM.ByteCode.Tests/ClassReaderTests.cs
new file mode 100644
index 0000000000..6b8f8b2631
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/ClassReaderTests.cs
@@ -0,0 +1,261 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+
+using FluentAssertions;
+
+using IKVM.ByteCode.Parsing;
+using IKVM.ByteCode.Reading;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.ByteCode.Tests
+{
+
+ [TestClass]
+ public class ClassReaderTests
+ {
+
+ [TestMethod]
+ public async Task CanLoadClassAsync()
+ {
+ using var file = File.OpenRead(Path.Combine(Path.GetDirectoryName(typeof(ClassReaderTests).Assembly.Location), "0.class"));
+ var clazz = await ClassReader.ReadAsync(file);
+ clazz.Should().NotBeNull();
+ clazz.This.Name.Value.Should().Be("0");
+ clazz.Constants.ToList();
+ clazz.Interfaces.ToList();
+ clazz.Fields.Should().HaveCount(0);
+ clazz.Fields.ToList();
+ clazz.Methods.Should().HaveCount(2);
+ clazz.Methods.ToList();
+
+ clazz.Methods[0].Attributes.Code.Code.Should().NotBeNull();
+ clazz.Methods[1].Attributes.Code.Code.Should().NotBeNull();
+ }
+
+ [TestMethod]
+ public void CanLoadClass()
+ {
+ using var file = File.OpenRead(Path.Combine(Path.GetDirectoryName(typeof(ClassReaderTests).Assembly.Location), "0.class"));
+ var clazz = ClassReader.Read(file);
+ clazz.Should().NotBeNull();
+ clazz.This.Name.Value.Should().Be("0");
+ clazz.Constants.ToList();
+ clazz.Interfaces.ToList();
+ clazz.Fields.Should().HaveCount(0);
+ clazz.Fields.ToList();
+ clazz.Methods.Should().HaveCount(2);
+ clazz.Methods.ToList();
+
+ clazz.Methods[0].Attributes.Code.Code.Should().NotBeNull();
+ clazz.Methods[1].Attributes.Code.Code.Should().NotBeNull();
+ }
+
+ [TestMethod]
+ public void CanLoadValidClassFiles()
+ {
+ var d = Path.Combine(Path.GetDirectoryName(typeof(ClassReaderTests).Assembly.Location), "resources");
+ var l = Directory.GetFiles(d, "*.class", SearchOption.AllDirectories);
+
+ foreach (var i in l)
+ {
+ using var f = File.OpenRead(i);
+ var c = ClassReader.Read(f);
+ c.This.Should().NotBeNull();
+ c.Constants.ToList();
+
+ foreach (var constant in c.Constants)
+ TestConstant(constant);
+
+ c.Interfaces.ToList();
+ c.Interfaces.Should().OnlyHaveUniqueItems();
+ c.Fields.ToList();
+ c.Fields.Should().OnlyHaveUniqueItems();
+ c.Methods.ToList();
+ c.Methods.Should().OnlyHaveUniqueItems();
+
+ foreach (var iface in c.Interfaces)
+ iface.Class.Name.Value.Should().NotBeNull();
+
+ foreach (var field in c.Fields)
+ {
+ field.Should().NotBeNull();
+ field.Name.Value.Should().NotBeNull();
+ field.Descriptor.Value.Should().NotBeNull();
+ field.Attributes.ToList();
+
+ foreach (var attribute in field.Attributes)
+ TestAttribute(attribute);
+ }
+
+ foreach (var method in c.Methods)
+ {
+ method.Name.Should().NotBeNull();
+ method.Descriptor.Should().NotBeNull();
+ method.Attributes.ToList();
+
+ foreach (var attribute in method.Attributes)
+ TestAttribute(attribute);
+ }
+
+ c.Attributes.ToList();
+ foreach (var attribute in c.Attributes)
+ TestAttribute(attribute);
+ }
+ }
+
+ void TestConstant(IConstantReader constant)
+ {
+ if (constant is Utf8ConstantReader utf8)
+ TestConstant(utf8);
+ if (constant is IntegerConstantReader integer)
+ TestConstant(integer);
+ if (constant is MethodHandleConstantReader methodHandle)
+ TestConstant(methodHandle);
+ }
+
+ void TestConstant(Utf8ConstantReader utf8)
+ {
+ utf8.Value.Should().NotBeNull();
+ }
+
+ void TestConstant(IntegerConstantReader integer)
+ {
+ integer.Value.GetType().Should().Be(typeof(int));
+ }
+
+ void TestConstant(MethodHandleConstantReader methodHandle)
+ {
+ if (methodHandle.ReferenceKind is ReferenceKind.GetField or ReferenceKind.GetStatic or ReferenceKind.PutField or ReferenceKind.PutStatic)
+ methodHandle.Reference.Should().BeOfType();
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeVirtual or ReferenceKind.NewInvokeSpecial)
+ methodHandle.Reference.Should().BeOfType();
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeStatic or ReferenceKind.InvokeSpecial && methodHandle.DeclaringClass.Version < new ClassFormatVersion(52, 0))
+ methodHandle.Reference.Should().BeOfType();
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeStatic or ReferenceKind.InvokeSpecial && methodHandle.DeclaringClass.Version >= new ClassFormatVersion(52, 0))
+ methodHandle.Reference.Should().Match(i => i is MethodrefConstantReader || i is InterfaceMethodrefConstantReader);
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeInterface)
+ methodHandle.Reference.Should().BeOfType();
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeVirtual or ReferenceKind.InvokeStatic or ReferenceKind.InvokeSpecial or ReferenceKind.InvokeInterface && methodHandle.Reference is MethodrefConstantReader methodRef)
+ methodRef.NameAndType.Name.Value.Should().NotBe("").And.NotBe("");
+ if (methodHandle.ReferenceKind is ReferenceKind.InvokeVirtual or ReferenceKind.InvokeStatic or ReferenceKind.InvokeSpecial or ReferenceKind.InvokeInterface && methodHandle.Reference is InterfaceMethodrefConstantReader interfaceMethodRef)
+ interfaceMethodRef.Class.Name.Value.Should().NotBe("").And.NotBe("");
+ }
+
+ void TestAttribute(AttributeReader attribute)
+ {
+ if (attribute is RuntimeVisibleAnnotationsAttributeReader runtimeVisibleAnnotationsAttributeReader)
+ TestAttribute(runtimeVisibleAnnotationsAttributeReader);
+ if (attribute is RuntimeInvisibleAnnotationsAttributeReader runtimeInvisibleAnnotationsAttributeReader)
+ TestAttribute(runtimeInvisibleAnnotationsAttributeReader);
+ if (attribute is RuntimeVisibleTypeAnnotationsAttributeReader runtimeVisibleTypeAnnotationsAttributeReader)
+ TestAttribute(runtimeVisibleTypeAnnotationsAttributeReader);
+ if (attribute is RuntimeInvisibleTypeAnnotationsAttributeReader runtimeInvisibleTypeAnnotationsAttributeReader)
+ TestAttribute(runtimeInvisibleTypeAnnotationsAttributeReader);
+ }
+
+ void TestAttribute(RuntimeVisibleAnnotationsAttributeReader attribute)
+ {
+ attribute.Info.Name.Value.Should().NotBeEmpty();
+ TestAnnotations(attribute.Annotations);
+ }
+
+ void TestAttribute(RuntimeInvisibleAnnotationsAttributeReader attribute)
+ {
+ attribute.Info.Name.Value.Should().NotBeEmpty();
+ TestAnnotations(attribute.Annotations);
+ }
+
+ void TestAttribute(RuntimeVisibleTypeAnnotationsAttributeReader attribute)
+ {
+ attribute.Info.Name.Value.Should().NotBeEmpty();
+ TestAnnotations(attribute.Annotations);
+ }
+
+ void TestAttribute(RuntimeInvisibleTypeAnnotationsAttributeReader attribute)
+ {
+ attribute.Info.Name.Value.Should().NotBeEmpty();
+ TestAnnotations(attribute.Annotations);
+ }
+
+ void TestAnnotations(IReadOnlyList annotations)
+ {
+ foreach (var annotation in annotations)
+ TestAnnotation(annotation);
+ }
+
+ void TestAnnotations(IReadOnlyList annotations)
+ {
+ foreach (var annotation in annotations)
+ TestAnnotation(annotation);
+ }
+
+ void TestAnnotation(AnnotationReader annotation)
+ {
+ annotation.Type.Value.Should().NotBeEmpty();
+ TestElementValuePair(annotation.Elements);
+ }
+
+ void TestAnnotation(TypeAnnotationReader annotation)
+ {
+ annotation.Type.Value.Should().NotBeEmpty();
+ TestElementValuePair(annotation.Elements);
+ }
+
+ void TestElementValuePair(ElementValueKeyReaderCollection elements)
+ {
+ elements.Count.Should().BeLessThan(256);
+
+ foreach (var element in elements)
+ TestElement(element.Key, element.Value);
+ }
+
+ void TestElement(string name, ElementValueReader value)
+ {
+ name.Should().NotBeEmpty();
+ value.Should().NotBeNull();
+
+ TestElementValue(value);
+ }
+
+ void TestElementValue(ElementValueReader value)
+ {
+ if (value is ElementValueConstantReader elementValueConstantReader)
+ TestElementValue(elementValueConstantReader);
+ if (value is ElementValueAnnotationReader elementAnnotationValueReader)
+ TestElementValue(elementAnnotationValueReader);
+ if (value is ElementValueArrayReader elementArrayValueReader)
+ TestElementValue(elementArrayValueReader);
+ if (value is ElementValueClassReader elementClassInfoValueReader)
+ TestElementValue(elementClassInfoValueReader);
+ }
+
+ void TestElementValue(ElementValueConstantReader elementValueConstantReader)
+ {
+ Enum.GetName(typeof(ElementValueTag), elementValueConstantReader.Tag).Should().NotBeNullOrEmpty();
+ elementValueConstantReader.Value.Should().NotBeNull();
+ TestConstant(elementValueConstantReader.Value);
+ }
+
+ void TestElementValue(ElementValueClassReader elementClassInfoValueReader)
+ {
+ elementClassInfoValueReader.Class.Value.Should().NotBeEmpty();
+ }
+
+ void TestElementValue(ElementValueAnnotationReader elementAnnotationValueReader)
+ {
+ TestAnnotation(elementAnnotationValueReader.Annotation);
+ }
+
+ void TestElementValue(ElementValueArrayReader elementArrayValueReader)
+ {
+ foreach (var value in elementArrayValueReader.Values)
+ TestElementValue(value);
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode.Tests/ClassVersionTests.cs b/src/IKVM.ByteCode.Tests/ClassVersionTests.cs
new file mode 100644
index 0000000000..4c8e2066f6
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/ClassVersionTests.cs
@@ -0,0 +1,69 @@
+using FluentAssertions;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.ByteCode.Tests
+{
+
+ [TestClass]
+ public class ClassVersionTests
+ {
+
+ [TestMethod]
+ public void CompareToShouldBeZeroForEqual()
+ {
+ new ClassFormatVersion(1, 0).CompareTo(new ClassFormatVersion(1, 0)).Should().Be(0);
+ }
+
+ [TestMethod]
+ public void CompareToShouldBeNegativeOneForGreaterMajor()
+ {
+ new ClassFormatVersion(1, 0).CompareTo(new ClassFormatVersion(2, 0)).Should().Be(-1);
+ }
+
+ [TestMethod]
+ public void CompareToShouldBeNegativeOneForGreaterMinor()
+ {
+ new ClassFormatVersion(1, 0).CompareTo(new ClassFormatVersion(1, 1)).Should().Be(-1);
+ }
+
+ [TestMethod]
+ public void CompareToShouldBeOneForGreaterMajor()
+ {
+ new ClassFormatVersion(2, 0).CompareTo(new ClassFormatVersion(1, 0)).Should().Be(1);
+ }
+
+ [TestMethod]
+ public void CompareToShouldBeOneForGreaterMinor()
+ {
+ new ClassFormatVersion(1, 1).CompareTo(new ClassFormatVersion(1, 0)).Should().Be(1);
+ }
+
+ [TestMethod]
+ public void ImplicitOperatorsShouldReturnCorrectValues()
+ {
+ (new ClassFormatVersion(1, 0) > new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(1, 0) < new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(1, 0) >= new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(1, 0) <= new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(1, 0) == new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(1, 0) != new ClassFormatVersion(1, 0)).Should().BeFalse();
+
+ (new ClassFormatVersion(1, 1) > new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(1, 1) < new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(1, 1) >= new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(1, 1) <= new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(1, 1) == new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(1, 1) != new ClassFormatVersion(1, 0)).Should().BeTrue();
+
+ (new ClassFormatVersion(2, 0) > new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(2, 0) < new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(2, 0) >= new ClassFormatVersion(1, 0)).Should().BeTrue();
+ (new ClassFormatVersion(2, 0) <= new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(2, 0) == new ClassFormatVersion(1, 0)).Should().BeFalse();
+ (new ClassFormatVersion(2, 0) != new ClassFormatVersion(1, 0)).Should().BeTrue();
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode.Tests/IKVM.ByteCode.Tests.csproj b/src/IKVM.ByteCode.Tests/IKVM.ByteCode.Tests.csproj
new file mode 100644
index 0000000000..ac7a2377c0
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/IKVM.ByteCode.Tests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net461;netcoreapp3.1;net6.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
diff --git a/src/IKVM.ByteCode.Tests/Parsing/TypeAnnotationRecordTests.cs b/src/IKVM.ByteCode.Tests/Parsing/TypeAnnotationRecordTests.cs
new file mode 100644
index 0000000000..d9e21eb130
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/Parsing/TypeAnnotationRecordTests.cs
@@ -0,0 +1,45 @@
+using System;
+
+using FluentAssertions;
+
+using IKVM.ByteCode.Parsing;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.ByteCode.Tests.Parsing
+{
+
+ [TestClass]
+ public class TypeAnnotationRecordTests
+ {
+
+ [TestMethod]
+ public void CanRoundTripTypeAnnotation()
+ {
+ var a = new TypeAnnotationRecord(
+ TypeAnnotationTargetType.Field,
+ new TypeAnnotationEmptyTargetRecord(),
+ new TypePathRecord(new TypePathItemRecord(TypePathKind.ArrayType, 0)),
+ 1,
+ new ElementValuePairRecord(2, new ElementValueRecord(ElementValueTag.Integer, new ElementValueConstantValueRecord(3))));
+
+ var b1 = new byte[a.GetSize()];
+ var w1 = new ClassFormatWriter(b1);
+ if (a.TryWrite(ref w1) == false)
+ throw new Exception();
+
+ var r = new ClassFormatReader(b1);
+ if (TypeAnnotationRecord.TryReadTypeAnnotation(ref r, out var b) == false)
+ throw new Exception();
+
+ var b2 = new byte[b.GetSize()];
+ var w2 = new ClassFormatWriter(b2);
+ if (b.TryWrite(ref w2) == false)
+ throw new Exception();
+
+ b1.Should().BeEquivalentTo(b2);
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.class b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.class
new file mode 100644
index 0000000000..e2af1771c0
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.class differ
diff --git a/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.cs b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.cs
new file mode 100644
index 0000000000..236b0f24b4
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.cs
@@ -0,0 +1,75 @@
+using System.IO;
+using System.Linq;
+
+using FluentAssertions;
+
+using IKVM.ByteCode.Reading;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.ByteCode.Tests.Reading
+{
+
+ [TestClass]
+ public class ConstantReaderTests
+ {
+
+ ClassReader ReadClass()
+ {
+ var f = File.OpenRead(Path.Combine(Path.GetDirectoryName(typeof(ConstantReaderTests).Assembly.Location), "Reading", "ConstantReaderTests.class"));
+ var c = ClassReader.Read(f);
+ return c;
+ }
+
+ [TestMethod]
+ public void CanReadIntegerConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Value == 394892);
+ }
+
+ [TestMethod]
+ public void CanReadLongConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Value == 34182132);
+ }
+
+ [TestMethod]
+ public void CanReadFloatConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Value == 221.03f);
+ }
+
+ [TestMethod]
+ public void CanReadDoubleConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Value == 2212133.1d);
+ }
+
+ [TestMethod]
+ public void CanReadStringConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Value == "STRING");
+ }
+
+ [TestMethod]
+ public void CanReadClassConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Name.Value == "java/lang/Object");
+ }
+
+ [TestMethod]
+ public void CanReadMethodrefConstant()
+ {
+ var c = ReadClass();
+ c.Constants.OfType().Should().Contain(i => i.Class.Name.Value == "java/lang/Object" && i.NameAndType.Name.Value == "" && i.NameAndType.Type.Value == "()V");
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.java b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.java
new file mode 100644
index 0000000000..e0d8907c7a
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/Reading/ConstantReaderTests.java
@@ -0,0 +1,9 @@
+final class ConstantReaderTests {
+
+ static int i = 394892;
+ static long j = 34182132;
+ static float f = 221.03f;
+ static double d = 2212133.1;
+ static String o = "STRING";
+
+}
diff --git a/src/IKVM.ByteCode.Tests/Text/MUTF8EncodingTests.cs b/src/IKVM.ByteCode.Tests/Text/MUTF8EncodingTests.cs
new file mode 100644
index 0000000000..ea79075aab
--- /dev/null
+++ b/src/IKVM.ByteCode.Tests/Text/MUTF8EncodingTests.cs
@@ -0,0 +1,87 @@
+using System;
+using System.Linq;
+
+using FluentAssertions;
+
+using IKVM.ByteCode.Text;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+namespace IKVM.ByteCode.Tests.Text
+{
+
+ [TestClass]
+ public class MUTF8EncodingTests
+ {
+
+ string asciiChars = new string(Enumerable.Range(32, 126 - 32).Select(i => (char)i).ToArray());
+
+ [TestMethod]
+ public void CanRoundTripASCIICharactersForJDK_1_4()
+ {
+ var e = MUTF8Encoding.GetMUTF8(48);
+ var b = e.GetBytes(asciiChars);
+ var c = e.GetString(b);
+ c.Should().Be(asciiChars);
+ }
+
+ [TestMethod]
+ public void CanRoundTripASCIICharactersForJDK_1_0()
+ {
+ var e = MUTF8Encoding.GetMUTF8(46);
+ var b = e.GetBytes(asciiChars);
+ var c = e.GetString(b);
+ c.Should().Be(asciiChars);
+ }
+
+ [TestMethod]
+ public void CanEncodeNullByte()
+ {
+ var l = MUTF8Encoding.GetMUTF8(48).GetByteCount("\0");
+ l.Should().Be(2);
+ var b = MUTF8Encoding.GetMUTF8(48).GetBytes("\0");
+ b.Should().HaveCount(2);
+ b[0].Should().Be(0b11000000);
+ b[1].Should().Be(0b10000000);
+ }
+
+ [TestMethod]
+ public void CanDecodeNullByte()
+ {
+ var s = MUTF8Encoding.GetMUTF8(48).GetChars(new byte[] { 0b11000000, 0b10000000 });
+ s.Should().HaveCount(1);
+ s[0].Should().Be('\0');
+ }
+
+ [TestMethod]
+ public unsafe void CanFindNullByte()
+ {
+ fixed (byte* ptr = new byte[] { 0x01, 0x00 })
+ MUTF8Encoding.GetMUTF8(48).IndexOfNull(ptr).Should().Be(1);
+ fixed (byte* ptr = new byte[] { 0x00, 0x00 })
+ MUTF8Encoding.GetMUTF8(48).IndexOfNull(ptr).Should().Be(0);
+ fixed (byte* ptr = new byte[] { 0x01, 0x01, 0x00 })
+ MUTF8Encoding.GetMUTF8(48).IndexOfNull(ptr).Should().Be(2);
+ }
+
+ [TestMethod]
+ public void CanEncodeNull()
+ {
+ MUTF8Encoding.GetMUTF8(48).GetBytes("\0").Should().HaveCount(2);
+ MUTF8Encoding.GetMUTF8(48).GetBytes("a\0").Should().HaveCount(3);
+ MUTF8Encoding.GetMUTF8(48).GetBytes("a\0a").Should().HaveCount(4);
+ MUTF8Encoding.GetMUTF8(48).GetBytes("\0\0").Should().HaveCount(4);
+ MUTF8Encoding.GetMUTF8(48).GetBytes("a\0\0").Should().HaveCount(5);
+ MUTF8Encoding.GetMUTF8(48).GetBytes("a\0\0a").Should().HaveCount(6);
+ }
+
+ [TestMethod]
+ public void CanHandleEmptyString()
+ {
+ MUTF8Encoding.GetMUTF8(48).GetBytes("").Should().BeEmpty();
+ MUTF8Encoding.GetMUTF8(48).GetString(Array.Empty()).Should().Be("");
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode.Tests/resources/CharSequence.class b/src/IKVM.ByteCode.Tests/resources/CharSequence.class
new file mode 100644
index 0000000000..29734d2c09
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/CharSequence.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/ColorEditor.class b/src/IKVM.ByteCode.Tests/resources/ColorEditor.class
new file mode 100644
index 0000000000..30132d7b9b
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/ColorEditor.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/Databinding$Builder.class b/src/IKVM.ByteCode.Tests/resources/Databinding$Builder.class
new file mode 100644
index 0000000000..8e35c60e98
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/Databinding$Builder.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/Databinding.class b/src/IKVM.ByteCode.Tests/resources/Databinding.class
new file mode 100644
index 0000000000..8d71ac405f
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/Databinding.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/DatabindingFactory.class b/src/IKVM.ByteCode.Tests/resources/DatabindingFactory.class
new file mode 100644
index 0000000000..2af6a40b1f
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/DatabindingFactory.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/DatabindingMode.class b/src/IKVM.ByteCode.Tests/resources/DatabindingMode.class
new file mode 100644
index 0000000000..a7778f88f4
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/DatabindingMode.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature$Builder.class b/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature$Builder.class
new file mode 100644
index 0000000000..8d486c4107
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature$Builder.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature.class b/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature.class
new file mode 100644
index 0000000000..1d75b37720
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/DatabindingModeFeature.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/DefaultPackage.class b/src/IKVM.ByteCode.Tests/resources/DefaultPackage.class
new file mode 100644
index 0000000000..0e6cce8c66
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/DefaultPackage.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/Serializable.class b/src/IKVM.ByteCode.Tests/resources/Serializable.class
new file mode 100644
index 0000000000..bde4e91583
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/Serializable.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/ThreadDeath.class b/src/IKVM.ByteCode.Tests/resources/ThreadDeath.class
new file mode 100644
index 0000000000..9b59f5237f
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/ThreadDeath.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/ICA.class b/src/IKVM.ByteCode.Tests/resources/annotations/ICA.class
new file mode 100644
index 0000000000..bb479f5bc1
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/ICA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/IFA.class b/src/IKVM.ByteCode.Tests/resources/annotations/IFA.class
new file mode 100644
index 0000000000..c3cd37f5be
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/IFA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/IMA.class b/src/IKVM.ByteCode.Tests/resources/annotations/IMA.class
new file mode 100644
index 0000000000..cee7bd63f2
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/IMA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/IPA.class b/src/IKVM.ByteCode.Tests/resources/annotations/IPA.class
new file mode 100644
index 0000000000..816a37cb94
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/IPA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/ITA.class b/src/IKVM.ByteCode.Tests/resources/annotations/ITA.class
new file mode 100644
index 0000000000..c05ccb2d97
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/ITA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/ITPA.class b/src/IKVM.ByteCode.Tests/resources/annotations/ITPA.class
new file mode 100644
index 0000000000..3734e57f70
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/ITPA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/ITUA.class b/src/IKVM.ByteCode.Tests/resources/annotations/ITUA.class
new file mode 100644
index 0000000000..374440360c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/ITUA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/IVA.class b/src/IKVM.ByteCode.Tests/resources/annotations/IVA.class
new file mode 100644
index 0000000000..e5de15cd48
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/IVA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VCA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VCA.class
new file mode 100644
index 0000000000..9cdf532bfb
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VCA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VFA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VFA.class
new file mode 100644
index 0000000000..59ffcf8d0d
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VFA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VMA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VMA.class
new file mode 100644
index 0000000000..82c909bebf
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VMA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VPA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VPA.class
new file mode 100644
index 0000000000..d9e6e5ba1c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VPA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VTA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VTA.class
new file mode 100644
index 0000000000..84c0a95772
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VTA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VTPA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VTPA.class
new file mode 100644
index 0000000000..2d0b3b9281
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VTPA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VTUA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VTUA.class
new file mode 100644
index 0000000000..d532458a2f
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VTUA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/annotations/VVA.class b/src/IKVM.ByteCode.Tests/resources/annotations/VVA.class
new file mode 100644
index 0000000000..378a3e73f8
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/annotations/VVA.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidBytecodeOffset.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidBytecodeOffset.clazz
new file mode 100644
index 0000000000..bc5286cc2a
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidBytecodeOffset.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidClassVersion.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidClassVersion.clazz
new file mode 100644
index 0000000000..1ef8a0b116
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidClassVersion.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCodeLength.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCodeLength.clazz
new file mode 100644
index 0000000000..8378e4f4ec
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCodeLength.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolIndex.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolIndex.clazz
new file mode 100644
index 0000000000..52360e85ab
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolIndex.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolReference.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolReference.clazz
new file mode 100644
index 0000000000..f0a7874f3e
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidConstantPoolReference.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCpInfoTag.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCpInfoTag.clazz
new file mode 100644
index 0000000000..650e8ac121
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidCpInfoTag.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidElementValue.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidElementValue.clazz
new file mode 100644
index 0000000000..c6cf72e044
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidElementValue.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidInsnTypeAnnotationTargetType.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidInsnTypeAnnotationTargetType.clazz
new file mode 100644
index 0000000000..5cbb5ac5c7
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidInsnTypeAnnotationTargetType.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidOpcode.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidOpcode.clazz
new file mode 100644
index 0000000000..3c94fceb23
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidOpcode.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidSourceDebugExtension.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidSourceDebugExtension.clazz
new file mode 100644
index 0000000000..f7df0e823c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidSourceDebugExtension.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidStackMapFrameType.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidStackMapFrameType.clazz
new file mode 100644
index 0000000000..3adc7b76d7
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidStackMapFrameType.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidTypeAnnotationTargetType.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidTypeAnnotationTargetType.clazz
new file mode 100644
index 0000000000..8859aa382c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidTypeAnnotationTargetType.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidVerificationTypeInfo.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidVerificationTypeInfo.clazz
new file mode 100644
index 0000000000..bea541c093
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidVerificationTypeInfo.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/invalid/InvalidWideOpcode.clazz b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidWideOpcode.clazz
new file mode 100644
index 0000000000..59d01081b7
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/invalid/InvalidWideOpcode.clazz differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk11/AllInstructions.class b/src/IKVM.ByteCode.Tests/resources/jdk11/AllInstructions.class
new file mode 100644
index 0000000000..69aa44471c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk11/AllInstructions.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures$Nested.class b/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures$Nested.class
new file mode 100644
index 0000000000..9d31e7bed0
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures$Nested.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures.class
new file mode 100644
index 0000000000..3aa1138bf4
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk11/AllStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$EmptyRecord.class b/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$EmptyRecord.class
new file mode 100644
index 0000000000..e2f8cb3ce4
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$EmptyRecord.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$RecordSubType.class b/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$RecordSubType.class
new file mode 100644
index 0000000000..61d0a18030
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk14/AllStructures$RecordSubType.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk15/AllStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk15/AllStructures.class
new file mode 100644
index 0000000000..851fc92f30
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk15/AllStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/AllInstructions.class b/src/IKVM.ByteCode.Tests/resources/jdk3/AllInstructions.class
new file mode 100644
index 0000000000..283d41a0d0
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/AllInstructions.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$1.class b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$1.class
new file mode 100644
index 0000000000..c21668ed12
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$1.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$InnerClass.class b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$InnerClass.class
new file mode 100644
index 0000000000..930cad3270
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures$InnerClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures.class
new file mode 100644
index 0000000000..c5b7ba2c1c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/AllStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/ArtificialStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk3/ArtificialStructures.class
new file mode 100644
index 0000000000..0ba4240b6d
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/ArtificialStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk3/LargeMethod.class b/src/IKVM.ByteCode.Tests/resources/jdk3/LargeMethod.class
new file mode 100644
index 0000000000..d2547710c4
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk3/LargeMethod.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllInstructions.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllInstructions.class
new file mode 100644
index 0000000000..f3d08c37c7
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllInstructions.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$1LocalClass.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$1LocalClass.class
new file mode 100644
index 0000000000..b6ac40da4c
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$1LocalClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$EnumClass.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$EnumClass.class
new file mode 100644
index 0000000000..23540f193b
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$EnumClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$GenericInnerClass.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$GenericInnerClass.class
new file mode 100644
index 0000000000..5291fe7892
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$GenericInnerClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InnerClass.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InnerClass.class
new file mode 100644
index 0000000000..baeb6c4ad3
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InnerClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InvisibleAnnotation.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InvisibleAnnotation.class
new file mode 100644
index 0000000000..485df516a7
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures$InvisibleAnnotation.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures.class
new file mode 100644
index 0000000000..a7de553e83
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk5/AllStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/AllFrames.class b/src/IKVM.ByteCode.Tests/resources/jdk8/AllFrames.class
new file mode 100644
index 0000000000..90d79b8c5b
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/AllFrames.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/AllInstructions.class b/src/IKVM.ByteCode.Tests/resources/jdk8/AllInstructions.class
new file mode 100644
index 0000000000..ecd224fb08
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/AllInstructions.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$1.class b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$1.class
new file mode 100644
index 0000000000..24a5d7e5dc
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$1.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$InnerClass.class b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$InnerClass.class
new file mode 100644
index 0000000000..2e05aa5dfa
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures$InnerClass.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures.class b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures.class
new file mode 100644
index 0000000000..3069f601b5
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/AllStructures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/Artificial$()$Structures.class b/src/IKVM.ByteCode.Tests/resources/jdk8/Artificial$()$Structures.class
new file mode 100644
index 0000000000..1cc843ec6b
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/Artificial$()$Structures.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk8/LargeMethod.class b/src/IKVM.ByteCode.Tests/resources/jdk8/LargeMethod.class
new file mode 100644
index 0000000000..bace5c0c89
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk8/LargeMethod.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk9/module-info.class b/src/IKVM.ByteCode.Tests/resources/jdk9/module-info.class
new file mode 100644
index 0000000000..e7418e67bd
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk9/module-info.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/A.class b/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/A.class
new file mode 100644
index 0000000000..d33ea1d335
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/A.class differ
diff --git a/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/internal/AImpl.class b/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/internal/AImpl.class
new file mode 100644
index 0000000000..51a374f6c8
Binary files /dev/null and b/src/IKVM.ByteCode.Tests/resources/jdk9/pkg/internal/AImpl.class differ
diff --git a/src/IKVM.ByteCode/AccessFlag.cs b/src/IKVM.ByteCode/AccessFlag.cs
new file mode 100644
index 0000000000..26697481cd
--- /dev/null
+++ b/src/IKVM.ByteCode/AccessFlag.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ [Flags]
+ internal enum AccessFlag : ushort
+ {
+
+ ACC_PUBLIC = 0x0001,
+ ACC_FINAL = 0x0010,
+ ACC_SUPER = 0x0020,
+ ACC_INTERFACE = 0x0200,
+ ACC_ABSTRACT = 0x0400,
+ ACC_SYNTHETIC = 0x1000,
+ ACC_ANNOTATION = 0x2000,
+ ACC_ENUM = 0x4000,
+ ACC_MODULE = 0x8000,
+
+ }
+
+
+}
diff --git a/src/IKVM.ByteCode/Buffers/RawBitConverter.cs b/src/IKVM.ByteCode/Buffers/RawBitConverter.cs
new file mode 100644
index 0000000000..05699b8f5b
--- /dev/null
+++ b/src/IKVM.ByteCode/Buffers/RawBitConverter.cs
@@ -0,0 +1,80 @@
+using System.Runtime.CompilerServices;
+
+#if NETCOREAPP3_1
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+#endif
+
+namespace IKVM.ByteCode.Buffers
+{
+
+ static class RawBitConverter
+ {
+
+#if NETFRAMEWORK || NETCOREAPP3_1
+
+ ///
+ /// Converts the specified 32-bit signed integer to a single-precision floating point number.
+ ///
+ /// The number to convert.
+ /// A single-precision floating point number whose bits are identical to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe float Int32BitsToSingle(int value)
+ {
+#if NETCOREAPP3_1
+ // Workaround for https://github.com/dotnet/runtime/issues/11413
+ if (Sse2.IsSupported)
+ {
+ Vector128 vec = Vector128.CreateScalarUnsafe(value).AsSingle();
+ return vec.ToScalar();
+ }
+#endif
+
+ return *((float*)&value);
+ }
+
+ ///
+ /// Converts the specified 32-bit signed integer to a single-precision floating point number.
+ ///
+ /// The number to convert.
+ /// A single-precision floating point number whose bits are identical to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe double Int64BitsToDouble(long value)
+ {
+#if NETCOREAPP3_1
+ // Workaround for https://github.com/dotnet/runtime/issues/11413
+ if (Sse2.X64.IsSupported)
+ {
+ Vector128 vec = Vector128.CreateScalarUnsafe(value).AsDouble();
+ return vec.ToScalar();
+ }
+#endif
+
+ return *((double*)&value);
+ }
+
+#endif
+
+#if NETFRAMEWORK || NETCOREAPP3_1
+
+ ///
+ /// Converts the specified 32-bit unsigned integer to a single-precision floating point number.
+ ///
+ /// The number to convert.
+ /// A single-precision floating point number whose bits are identical to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe float UInt32BitsToSingle(uint value) => Int32BitsToSingle((int)value);
+
+ ///
+ /// Converts the specified 32-bit unsigned integer to a single-precision floating point number.
+ ///
+ /// The number to convert.
+ /// A single-precision floating point number whose bits are identical to .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe double UInt64BitsToDouble(ulong value) => Int64BitsToDouble((long)value);
+
+#endif
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Buffers/SequenceReader.cs b/src/IKVM.ByteCode/Buffers/SequenceReader.cs
new file mode 100644
index 0000000000..c93fd648a8
--- /dev/null
+++ b/src/IKVM.ByteCode/Buffers/SequenceReader.cs
@@ -0,0 +1,505 @@
+// Copyright (c) All contributors. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+/* Licensed to the .NET Foundation under one or more agreements.
+ * The .NET Foundation licenses this file to you under the MIT license.
+ * See the LICENSE file in the project root for more information. */
+
+using System;
+using System.Buffers;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace IKVM.ByteCode.Buffers
+{
+
+#if NETFRAMEWORK
+
+ public ref partial struct SequenceReader
+ where T : unmanaged, IEquatable
+ {
+
+ ///
+ /// A value indicating whether we're using (as opposed to .
+ ///
+ private bool usingSequence;
+
+ ///
+ /// Backing for the entire sequence when we're not using .
+ ///
+ private ReadOnlySequence sequence;
+
+ ///
+ /// The position at the start of the .
+ ///
+ private SequencePosition currentPosition;
+
+ ///
+ /// The position at the end of the .
+ ///
+ private SequencePosition nextPosition;
+
+ ///
+ /// Backing for the entire sequence when we're not using .
+ ///
+ private ReadOnlyMemory memory;
+
+ ///
+ /// A value indicating whether there is unread data remaining.
+ ///
+ private bool moreData;
+
+ ///
+ /// The total number of elements in the sequence.
+ ///
+ private long length;
+
+ ///
+ /// Initializes a new instance of the struct
+ /// over the given .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SequenceReader(ReadOnlySequence sequence)
+ {
+ this.usingSequence = true;
+ this.CurrentSpanIndex = 0;
+ this.Consumed = 0;
+ this.sequence = sequence;
+ this.memory = default;
+ this.currentPosition = sequence.Start;
+ this.length = -1;
+
+ ReadOnlySpan first = sequence.First.Span;
+ this.nextPosition = sequence.GetPosition(first.Length);
+ this.CurrentSpan = first;
+ this.moreData = first.Length > 0;
+
+ if (!this.moreData && !sequence.IsSingleSegment)
+ {
+ this.moreData = true;
+ this.GetNextSpan();
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the struct
+ /// over the given .
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public SequenceReader(ReadOnlyMemory memory)
+ {
+ this.usingSequence = false;
+ this.CurrentSpanIndex = 0;
+ this.Consumed = 0;
+ this.memory = memory;
+ this.CurrentSpan = memory.Span;
+ this.length = memory.Length;
+ this.moreData = memory.Length > 0;
+
+ this.currentPosition = default;
+ this.nextPosition = default;
+ this.sequence = default;
+ }
+
+ ///
+ /// Gets a value indicating whether there is no more data in the .
+ ///
+ public bool End => !this.moreData;
+
+ ///
+ /// Gets the underlying for the reader.
+ ///
+ public ReadOnlySequence Sequence
+ {
+ get
+ {
+ if (this.sequence.IsEmpty && !this.memory.IsEmpty)
+ {
+ // We're in memory mode (instead of sequence mode).
+ // Lazily fill in the sequence data.
+ this.sequence = new ReadOnlySequence(this.memory);
+ this.currentPosition = this.sequence.Start;
+ this.nextPosition = this.sequence.End;
+ }
+
+ return this.sequence;
+ }
+ }
+
+ ///
+ /// Gets the current position in the .
+ ///
+ public SequencePosition Position
+ => this.Sequence.GetPosition(this.CurrentSpanIndex, this.currentPosition);
+
+ ///
+ /// Gets the current segment in the as a span.
+ ///
+ public ReadOnlySpan CurrentSpan { get; private set; }
+
+ ///
+ /// Gets the index in the .
+ ///
+ public int CurrentSpanIndex { get; private set; }
+
+ ///
+ /// Gets the unread portion of the .
+ ///
+ public ReadOnlySpan UnreadSpan
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => this.CurrentSpan.Slice(this.CurrentSpanIndex);
+ }
+
+ ///
+ /// Gets the total number of 's processed by the reader.
+ ///
+ public long Consumed { get; private set; }
+
+ ///
+ /// Gets remaining 's in the reader's .
+ ///
+ public long Remaining => this.Length - this.Consumed;
+
+ ///
+ /// Gets count of in the reader's .
+ ///
+ public long Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (this.length < 0)
+ {
+ // Cache the length
+ this.length = this.Sequence.Length;
+ }
+
+ return this.length;
+ }
+ }
+
+ ///
+ /// Peeks at the next value without advancing the reader.
+ ///
+ /// The next value or default if at the end.
+ /// False if at the end of the reader.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryPeek(out T value)
+ {
+ if (this.moreData)
+ {
+ value = this.CurrentSpan[this.CurrentSpanIndex];
+ return true;
+ }
+ else
+ {
+ value = default;
+ return false;
+ }
+ }
+
+ ///
+ /// Read the next value and advance the reader.
+ ///
+ /// The next value or default if at the end.
+ /// False if at the end of the reader.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryRead(out T value)
+ {
+ if (this.End)
+ {
+ value = default;
+ return false;
+ }
+
+ value = this.CurrentSpan[this.CurrentSpanIndex];
+ this.CurrentSpanIndex++;
+ this.Consumed++;
+
+ if (this.CurrentSpanIndex >= this.CurrentSpan.Length)
+ {
+ if (this.usingSequence)
+ {
+ this.GetNextSpan();
+ }
+ else
+ {
+ this.moreData = false;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Move the reader back the specified number of items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Rewind(long count)
+ {
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ this.Consumed -= count;
+
+ if (this.CurrentSpanIndex >= count)
+ {
+ this.CurrentSpanIndex -= (int)count;
+ this.moreData = true;
+ }
+ else if (this.usingSequence)
+ {
+ // Current segment doesn't have enough data, scan backward through segments
+ this.RetreatToPreviousSpan(this.Consumed);
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException("Rewind went past the start of the memory.");
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private void RetreatToPreviousSpan(long consumed)
+ {
+ Debug.Assert(this.usingSequence, "usingSequence");
+ this.ResetReader();
+ this.Advance(consumed);
+ }
+
+ private void ResetReader()
+ {
+ Debug.Assert(this.usingSequence, "usingSequence");
+ this.CurrentSpanIndex = 0;
+ this.Consumed = 0;
+ this.currentPosition = this.Sequence.Start;
+ this.nextPosition = this.currentPosition;
+
+ if (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true))
+ {
+ this.moreData = true;
+
+ if (memory.Length == 0)
+ {
+ this.CurrentSpan = default;
+
+ // No data in the first span, move to one with data
+ this.GetNextSpan();
+ }
+ else
+ {
+ this.CurrentSpan = memory.Span;
+ }
+ }
+ else
+ {
+ // No data in any spans and at end of sequence
+ this.moreData = false;
+ this.CurrentSpan = default;
+ }
+ }
+
+ ///
+ /// Get the next segment with available data, if any.
+ ///
+ private void GetNextSpan()
+ {
+ Debug.Assert(this.usingSequence, "usingSequence");
+ if (!this.Sequence.IsSingleSegment)
+ {
+ SequencePosition previousNextPosition = this.nextPosition;
+ while (this.Sequence.TryGet(ref this.nextPosition, out ReadOnlyMemory memory, advance: true))
+ {
+ this.currentPosition = previousNextPosition;
+ if (memory.Length > 0)
+ {
+ this.CurrentSpan = memory.Span;
+ this.CurrentSpanIndex = 0;
+ return;
+ }
+ else
+ {
+ this.CurrentSpan = default;
+ this.CurrentSpanIndex = 0;
+ previousNextPosition = this.nextPosition;
+ }
+ }
+ }
+
+ this.moreData = false;
+ }
+
+ ///
+ /// Move the reader ahead the specified number of items.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Advance(long count)
+ {
+ const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
+ if ((count & TooBigOrNegative) == 0 && this.CurrentSpan.Length - this.CurrentSpanIndex > (int)count)
+ {
+ this.CurrentSpanIndex += (int)count;
+ this.Consumed += count;
+ }
+ else if (this.usingSequence)
+ {
+ // Can't satisfy from the current span
+ this.AdvanceToNextSpan(count);
+ }
+ else if (this.CurrentSpan.Length - this.CurrentSpanIndex == (int)count)
+ {
+ this.CurrentSpanIndex += (int)count;
+ this.Consumed += count;
+ this.moreData = false;
+ }
+ else
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ }
+
+ ///
+ /// Unchecked helper to avoid unnecessary checks where you know count is valid.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void AdvanceCurrentSpan(long count)
+ {
+ Debug.Assert(count >= 0, "count >= 0");
+
+ this.Consumed += count;
+ this.CurrentSpanIndex += (int)count;
+ if (this.usingSequence && this.CurrentSpanIndex >= this.CurrentSpan.Length)
+ {
+ this.GetNextSpan();
+ }
+ }
+
+ ///
+ /// Only call this helper if you know that you are advancing in the current span
+ /// with valid count and there is no need to fetch the next one.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void AdvanceWithinSpan(long count)
+ {
+ Debug.Assert(count >= 0, "count >= 0");
+
+ this.Consumed += count;
+ this.CurrentSpanIndex += (int)count;
+
+ Debug.Assert(this.CurrentSpanIndex < this.CurrentSpan.Length, "this.CurrentSpanIndex < this.CurrentSpan.Length");
+ }
+
+ ///
+ /// Move the reader ahead the specified number of items
+ /// if there are enough elements remaining in the sequence.
+ ///
+ /// true if there were enough elements to advance; otherwise false.
+ internal bool TryAdvance(long count)
+ {
+ if (this.Remaining < count)
+ {
+ return false;
+ }
+
+ this.Advance(count);
+ return true;
+ }
+
+ private void AdvanceToNextSpan(long count)
+ {
+ Debug.Assert(this.usingSequence, "usingSequence");
+ if (count < 0)
+ {
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+
+ this.Consumed += count;
+ while (this.moreData)
+ {
+ int remaining = this.CurrentSpan.Length - this.CurrentSpanIndex;
+
+ if (remaining > count)
+ {
+ this.CurrentSpanIndex += (int)count;
+ count = 0;
+ break;
+ }
+
+ // As there may not be any further segments we need to
+ // push the current index to the end of the span.
+ this.CurrentSpanIndex += remaining;
+ count -= remaining;
+ Debug.Assert(count >= 0, "count >= 0");
+
+ this.GetNextSpan();
+
+ if (count == 0)
+ {
+ break;
+ }
+ }
+
+ if (count != 0)
+ {
+ // Not enough data left- adjust for where we actually ended and throw
+ this.Consumed -= count;
+ throw new ArgumentOutOfRangeException(nameof(count));
+ }
+ }
+
+ ///
+ /// Copies data from the current to the given span.
+ ///
+ /// Destination to copy to.
+ /// True if there is enough data to copy to the .
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool TryCopyTo(Span destination)
+ {
+ ReadOnlySpan firstSpan = this.UnreadSpan;
+ if (firstSpan.Length >= destination.Length)
+ {
+ firstSpan.Slice(0, destination.Length).CopyTo(destination);
+ return true;
+ }
+
+ return this.TryCopyMultisegment(destination);
+ }
+
+ internal bool TryCopyMultisegment(Span destination)
+ {
+ if (this.Remaining < destination.Length)
+ {
+ return false;
+ }
+
+ ReadOnlySpan firstSpan = this.UnreadSpan;
+ Debug.Assert(firstSpan.Length < destination.Length, "firstSpan.Length < destination.Length");
+ firstSpan.CopyTo(destination);
+ int copied = firstSpan.Length;
+
+ SequencePosition next = this.nextPosition;
+ while (this.Sequence.TryGet(ref next, out ReadOnlyMemory nextSegment, true))
+ {
+ if (nextSegment.Length > 0)
+ {
+ ReadOnlySpan nextSpan = nextSegment.Span;
+ int toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
+ nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
+ copied += toCopy;
+ if (copied >= destination.Length)
+ {
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ }
+
+#endif
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Buffers/SequenceReaderExtensions.cs b/src/IKVM.ByteCode/Buffers/SequenceReaderExtensions.cs
new file mode 100644
index 0000000000..e60681a68f
--- /dev/null
+++ b/src/IKVM.ByteCode/Buffers/SequenceReaderExtensions.cs
@@ -0,0 +1,284 @@
+using System;
+using System.Buffers;
+using System.Buffers.Binary;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace IKVM.ByteCode.Buffers
+{
+
+ static partial class SequenceReaderExtensions
+ {
+
+ ///
+ /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
+ /// structs- see remarks for full details.
+ ///
+ ///
+ /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of its members to
+ /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
+ /// overloads such as .
+ ///
+ ///
+ /// True if successful. will be default if failed (due to lack of space).
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe bool TryRead(ref this SequenceReader reader, out T value)
+ where T : unmanaged
+ {
+ var span = reader.UnreadSpan;
+ if (span.Length < sizeof(T))
+ return TryReadMultisegment(ref reader, out value);
+
+ value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span));
+ reader.Advance(sizeof(T));
+ return true;
+ }
+
+ static unsafe bool TryReadMultisegment(ref SequenceReader reader, out T value)
+ where T : unmanaged
+ {
+ Debug.Assert(reader.UnreadSpan.Length < sizeof(T), "reader.UnreadSpan.Length < sizeof(T)");
+
+ // Not enough data in the current segment, try to peek for the data we need.
+ var buffer = default(T);
+ var tempSpan = new Span(&buffer, sizeof(T));
+
+ if (!reader.TryCopyTo(tempSpan))
+ {
+ value = default;
+ return false;
+ }
+
+ value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan));
+ reader.Advance(sizeof(T));
+ return true;
+ }
+
+ ///
+ /// Reads an from the next position in the sequence.
+ ///
+ /// The reader to read from.
+ /// Receives the value read.
+ /// true if there was another byte in the sequence; false otherwise.
+ public static bool TryRead(ref this SequenceReader reader, out sbyte value)
+ {
+ if (TryRead(ref reader, out byte byteValue))
+ {
+ value = unchecked((sbyte)byteValue);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+#if NETFRAMEWORK
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out short value)
+ {
+ if (!BitConverter.IsLittleEndian)
+ {
+ return reader.TryRead(out value);
+ }
+
+ return TryReadReverseEndianness(ref reader, out value);
+ }
+
+#endif
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out ushort value)
+ {
+ if (reader.TryReadBigEndian(out short shortValue))
+ {
+ value = unchecked((ushort)shortValue);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ private static bool TryReadReverseEndianness(ref SequenceReader reader, out short value)
+ {
+ if (reader.TryRead(out value))
+ {
+ value = BinaryPrimitives.ReverseEndianness(value);
+ return true;
+ }
+
+ return false;
+ }
+
+#if NETFRAMEWORK
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out int value)
+ {
+ return BitConverter.IsLittleEndian ? TryReadReverseEndianness(ref reader, out value) : reader.TryRead(out value);
+ }
+
+#endif
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out uint value)
+ {
+ if (reader.TryReadBigEndian(out int intValue))
+ {
+ value = unchecked((uint)intValue);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ static bool TryReadReverseEndianness(ref SequenceReader reader, out int value)
+ {
+ if (reader.TryRead(out value))
+ {
+ value = BinaryPrimitives.ReverseEndianness(value);
+ return true;
+ }
+
+ return false;
+ }
+
+#if NETFRAMEWORK
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out long value)
+ {
+ return BitConverter.IsLittleEndian ? TryReadReverseEndianness(ref reader, out value) : reader.TryRead(out value);
+ }
+
+#endif
+
+ ///
+ /// Reads an as big endian.
+ ///
+ /// False if there wasn't enough data for an .
+ public static bool TryReadBigEndian(ref this SequenceReader reader, out ulong value)
+ {
+ if (reader.TryReadBigEndian(out long longValue))
+ {
+ value = unchecked((ulong)longValue);
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ static bool TryReadReverseEndianness(ref SequenceReader reader, out long value)
+ {
+ if (reader.TryRead(out value))
+ {
+ value = BinaryPrimitives.ReverseEndianness(value);
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Reads a as big endian.
+ ///
+ /// False if there wasn't enough data for a .
+ public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out float value)
+ {
+ if (reader.TryReadBigEndian(out int intValue))
+ {
+ value = *(float*)&intValue;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ ///
+ /// Reads a as big endian.
+ ///
+ /// False if there wasn't enough data for a .
+ public static unsafe bool TryReadBigEndian(ref this SequenceReader reader, out double value)
+ {
+ if (reader.TryReadBigEndian(out long longValue))
+ {
+ value = *(double*)&longValue;
+ return true;
+ }
+
+ value = default;
+ return false;
+ }
+
+ ///
+ /// Try to read data with given .
+ ///
+ /// Read count.
+ /// The read data, if successfully read requested data.
+ /// true if remaining items in current is enough for .
+ public static bool TryReadExact(ref this SequenceReader reader, int count, out ReadOnlySequence sequence)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (count > reader.Remaining)
+ {
+ sequence = default;
+ return false;
+ }
+
+ sequence = reader.Sequence.Slice(reader.Position, count);
+ if (count != 0)
+ reader.Advance(count);
+
+ return true;
+ }
+
+ ///
+ /// Try to read data with given .
+ ///
+ /// Read count.
+ /// The read data, if successfully read requested data.
+ /// true if remaining items in current is enough for .
+ public static bool TryReadExact(ref this SequenceReader reader, long count, out ReadOnlySequence sequence)
+ {
+ if (count < 0)
+ throw new ArgumentOutOfRangeException(nameof(count));
+
+ if (count > reader.Remaining)
+ {
+ sequence = default;
+ return false;
+ }
+
+ sequence = reader.Sequence.Slice(reader.Position, count);
+ if (count != 0)
+ reader.Advance(count);
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/ByteCodeException.cs b/src/IKVM.ByteCode/ByteCodeException.cs
new file mode 100644
index 0000000000..5a5ec57ace
--- /dev/null
+++ b/src/IKVM.ByteCode/ByteCodeException.cs
@@ -0,0 +1,66 @@
+using System;
+using System.Runtime.Serialization;
+
+namespace IKVM.ByteCode
+{
+
+ ///
+ /// Represents an error that occurred with manipulation of Java byte code or class formats.
+ ///
+ [Serializable]
+ internal class ByteCodeException : Exception
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ internal ByteCodeException()
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ internal ByteCodeException(string message) :
+ base(message)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ internal ByteCodeException(Exception innerException) :
+ base(innerException.Message, innerException)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal ByteCodeException(string message, Exception innerException) :
+ base(message, innerException)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ protected ByteCodeException(SerializationInfo info, StreamingContext context) :
+ base(info, context)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/ClassFormatVersion.cs b/src/IKVM.ByteCode/ClassFormatVersion.cs
new file mode 100644
index 0000000000..33896b9e9d
--- /dev/null
+++ b/src/IKVM.ByteCode/ClassFormatVersion.cs
@@ -0,0 +1,66 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ ///
+ /// Describes a Java Class file format version number, consisting of a major and minor pair.
+ ///
+ ///
+ ///
+ internal record struct ClassFormatVersion(int Major, int Minor) : IComparable
+ {
+
+ public static implicit operator ClassFormatVersion(int major)
+ {
+ return new ClassFormatVersion(major, 0);
+ }
+
+ public static implicit operator string(ClassFormatVersion version)
+ {
+ return version.ToString();
+ }
+
+ public static bool operator >(ClassFormatVersion a, ClassFormatVersion b)
+ {
+ return a.CompareTo(b) is 1;
+ }
+
+ public static bool operator <(ClassFormatVersion a, ClassFormatVersion b)
+ {
+ return a.CompareTo(b) is -1;
+ }
+
+ public static bool operator >=(ClassFormatVersion a, ClassFormatVersion b)
+ {
+ return a.CompareTo(b) is 0 or 1;
+ }
+
+ public static bool operator <=(ClassFormatVersion a, ClassFormatVersion b)
+ {
+ return a.CompareTo(b) is 0 or -1;
+ }
+
+ public int CompareTo(ClassFormatVersion other)
+ {
+ if (Major < other.Major)
+ return -1;
+ else if (Major > other.Major)
+ return 1;
+
+ if (Minor < other.Minor)
+ return -1;
+ else if (Minor > other.Minor)
+ return 1;
+
+ return 0;
+ }
+
+ public override string ToString()
+ {
+ return $"{Major}.{Minor}";
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Compiler/IsExternalInit.cs b/src/IKVM.ByteCode/Compiler/IsExternalInit.cs
new file mode 100644
index 0000000000..e251a1ef91
--- /dev/null
+++ b/src/IKVM.ByteCode/Compiler/IsExternalInit.cs
@@ -0,0 +1,22 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET6 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48
+
+using System.ComponentModel;
+
+// ReSharper disable once CheckNamespace
+namespace System.Runtime.CompilerServices
+{
+ ///
+ /// Reserved to be used by the compiler for tracking metadata.
+ /// This class should not be used by developers in source code.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ internal static class IsExternalInit
+ {
+ }
+}
+
+#endif
diff --git a/src/IKVM.ByteCode/IKVM.ByteCode.csproj b/src/IKVM.ByteCode/IKVM.ByteCode.csproj
new file mode 100644
index 0000000000..7c6ea6b483
--- /dev/null
+++ b/src/IKVM.ByteCode/IKVM.ByteCode.csproj
@@ -0,0 +1,32 @@
+
+
+
+ net461;netcoreapp3.1;net6.0
+ 11
+ true
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/IKVM.ByteCode/ModuleExportsFlag.cs b/src/IKVM.ByteCode/ModuleExportsFlag.cs
new file mode 100644
index 0000000000..791f2b0085
--- /dev/null
+++ b/src/IKVM.ByteCode/ModuleExportsFlag.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ [Flags]
+ internal enum ModuleExportsFlag : ushort
+ {
+
+ Synthetic = 0x1000,
+ Mandated = 0x8000,
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/ModuleFlag.cs b/src/IKVM.ByteCode/ModuleFlag.cs
new file mode 100644
index 0000000000..711ea82db7
--- /dev/null
+++ b/src/IKVM.ByteCode/ModuleFlag.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ [Flags]
+ internal enum ModuleFlag : ushort
+ {
+
+ Open = 0x0020,
+ Synthetic = 0x1000,
+ Mandated = 0x8000,
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/ModuleOpensFlag.cs b/src/IKVM.ByteCode/ModuleOpensFlag.cs
new file mode 100644
index 0000000000..ea6ade7d54
--- /dev/null
+++ b/src/IKVM.ByteCode/ModuleOpensFlag.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ [Flags]
+ internal enum ModuleOpensFlag : ushort
+ {
+
+ Synthetic = 0x1000,
+ Mandated = 0x8000,
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/ModuleRequiresFlag.cs b/src/IKVM.ByteCode/ModuleRequiresFlag.cs
new file mode 100644
index 0000000000..da6dc0457c
--- /dev/null
+++ b/src/IKVM.ByteCode/ModuleRequiresFlag.cs
@@ -0,0 +1,17 @@
+using System;
+
+namespace IKVM.ByteCode
+{
+
+ [Flags]
+ internal enum ModuleRequiresFlag : ushort
+ {
+
+ Transitive = 0x0020,
+ StaticPhase = 0x0040,
+ Synthetic = 0x1000,
+ Mandated = 0x8000,
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/AnnotationDefaultAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/AnnotationDefaultAttributeRecord.cs
new file mode 100644
index 0000000000..a714c0a5be
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/AnnotationDefaultAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record AnnotationDefaultAttributeRecord(ElementValueRecord DefaultValue) : AttributeRecord
+ {
+
+ public static bool TryReadAnnotationDefaultAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (ElementValueRecord.TryRead(ref reader, out var defaultValue) == false)
+ return false;
+
+ attribute = new AnnotationDefaultAttributeRecord(defaultValue);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/AnnotationRecord.cs b/src/IKVM.ByteCode/Parsing/AnnotationRecord.cs
new file mode 100644
index 0000000000..c7da556b50
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/AnnotationRecord.cs
@@ -0,0 +1,62 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct AnnotationRecord(ushort TypeIndex, ElementValuePairRecord[] Elements)
+ {
+
+ public static bool TryReadAnnotation(ref ClassFormatReader reader, out AnnotationRecord annotation)
+ {
+ annotation = default;
+
+ if (reader.TryReadU2(out ushort typeIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort pairCount) == false)
+ return false;
+
+ var elements = new ElementValuePairRecord[pairCount];
+ for (int i = 0; i < pairCount; i++)
+ {
+ if (ElementValuePairRecord.TryRead(ref reader, out var element) == false)
+ return false;
+
+ elements[i] = element;
+ }
+
+ annotation = new AnnotationRecord(typeIndex, elements);
+ return true;
+ }
+
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+ size += sizeof(ushort);
+
+ foreach (var element in Elements)
+ size += element.GetSize();
+
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(TypeIndex) == false)
+ return false;
+ if (writer.TryWriteU2((ushort)Elements.Length) == false)
+ return false;
+
+ foreach (var element in Elements)
+ if (element.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/AppendStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/AppendStackMapFrameRecord.cs
new file mode 100644
index 0000000000..255022276a
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/AppendStackMapFrameRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record AppendStackMapFrameRecord(byte Tag, ushort OffsetDelta, VerificationTypeInfoRecord[] Locals) : StackMapFrameRecord(Tag)
+ {
+
+ public static bool TryReadAppendStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU2(out ushort offsetDelta) == false)
+ return false;
+
+ var locals = new VerificationTypeInfoRecord[tag - 251];
+ for (int i = 0; i < tag - 251; i++)
+ {
+ if (VerificationTypeInfoRecord.TryReadVerificationTypeInfo(ref reader, out var local) == false)
+ return false;
+
+ locals[i] = local;
+ }
+
+ frame = new AppendStackMapFrameRecord(tag, offsetDelta, locals);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/AttributeInfoRecord.cs b/src/IKVM.ByteCode/Parsing/AttributeInfoRecord.cs
new file mode 100644
index 0000000000..a60786bd8b
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/AttributeInfoRecord.cs
@@ -0,0 +1,34 @@
+using System.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct AttributeInfoRecord(ushort NameIndex, byte[] Data)
+ {
+
+ ///
+ /// Parses an attribute.
+ ///
+ ///
+ ///
+ public static bool TryReadAttribute(ref ClassFormatReader reader, out AttributeInfoRecord attribute)
+ {
+ attribute = default;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU4(out uint length) == false)
+ return false;
+ if (reader.TryReadManyU1(length, out var info) == false)
+ return false;
+
+ var infoBuffer = new byte[info.Length];
+ info.CopyTo(infoBuffer);
+
+ attribute = new AttributeInfoRecord(nameIndex, infoBuffer);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/AttributeRecord.cs b/src/IKVM.ByteCode/Parsing/AttributeRecord.cs
new file mode 100644
index 0000000000..bc302ef9a8
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/AttributeRecord.cs
@@ -0,0 +1,83 @@
+using System.Buffers;
+
+using IKVM.ByteCode.Reading;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record AttributeRecord
+ {
+
+ ///
+ /// Attempts to read an attribute data record from the specified .
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(AttributeInfoReader info, out AttributeRecord attribute)
+ {
+ return TryRead(info.Name.Value, new ReadOnlySequence(info.Data), out attribute);
+ }
+
+ ///
+ /// Attempts to read an attribute data record from the specified .
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(string name, ReadOnlySequence data, out AttributeRecord attribute)
+ {
+ var reader = new ClassFormatReader(data);
+ if (TryReadAttribute(name, ref reader, out attribute) == false)
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Attempts to read a class record starting at the current position.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadAttribute(string name, ref ClassFormatReader reader, out AttributeRecord attribute) => name switch
+ {
+ "ConstantValue" => ConstantValueAttributeRecord.TryReadConstantValueAttribute(ref reader, out attribute),
+ "Code" => CodeAttributeRecord.TryReadCodeAttribute(ref reader, out attribute),
+ "StackMapTable" => StackMapTableAttributeRecord.TryRead(ref reader, out attribute),
+ "Exceptions" => ExceptionsAttributeRecord.TryReadExceptionsAttribute(ref reader, out attribute),
+ "InnerClasses" => InnerClassesAttributeRecord.TryReadInnerClassesAttribute(ref reader, out attribute),
+ "EnclosingMethod" => EnclosingMethodAttributeRecord.TryReadEnclosingMethodAttribute(ref reader, out attribute),
+ "Synthetic" => SyntheticAttributeRecord.TryReadSyntheticAttribute(ref reader, out attribute),
+ "Signature" => SignatureAttributeRecord.TryReadSignatureAttribute(ref reader, out attribute),
+ "SourceFile" => SourceFileAttributeRecord.TryReadSourceFileAttribute(ref reader, out attribute),
+ "SourceDebugExtension" => SourceDebugExtensionAttributeRecord.TryReadSourceDebugExtensionAttribute(ref reader, out attribute),
+ "LineNumberTable" => LineNumberTableAttributeRecord.TryReadLineNumberTableAttribute(ref reader, out attribute),
+ "LocalVariableTable" => LocalVariableTableAttributeRecord.TryReadLocalVariableTableAttribute(ref reader, out attribute),
+ "LocalVariableTypeTable" => LocalVariableTypeTableAttributeRecord.TryReadLocalVariableTypeTableAttribute(ref reader, out attribute),
+ "Deprecated" => DeprecatedAttributeRecord.TryReadDeprecatedAttribute(ref reader, out attribute),
+ "RuntimeVisibleAnnotations" => RuntimeVisibleAnnotationsAttributeRecord.TryReadRuntimeVisibleAnnotationsAttribute(ref reader, out attribute),
+ "RuntimeInvisibleAnnotations" => RuntimeInvisibleAnnotationsAttributeRecord.TryReadRuntimeInvisibleAnnotationsAttribute(ref reader, out attribute),
+ "RuntimeVisibleParameterAnnotations" => RuntimeVisibleParameterAnnotationsAttributeRecord.TryReadRuntimeVisibleParameterAnnotationsAttribute(ref reader, out attribute),
+ "RuntimeInvisibleParameterAnnotations" => RuntimeInvisibleParameterAnnotationsAttributeRecord.TryReadRuntimeInvisibleParameterAnnotationsAttribute(ref reader, out attribute),
+ "RuntimeVisibleTypeAnnotations" => RuntimeVisibleTypeAnnotationsAttributeRecord.TryReadRuntimeVisibleTypeAnnotationsAttribute(ref reader, out attribute),
+ "RuntimeInvisibleTypeAnnotations" => RuntimeInvisibleTypeAnnotationsAttributeRecord.TryReadRuntimeInvisibleTypeAnnotationsAttribute(ref reader, out attribute),
+ "AnnotationDefault" => AnnotationDefaultAttributeRecord.TryReadAnnotationDefaultAttribute(ref reader, out attribute),
+ "BootstrapMethods" => BootstrapMethodsAttributeRecord.TryReadBootstrapMethodsAttribute(ref reader, out attribute),
+ "MethodParameters" => MethodParametersAttributeRecord.TryReadMethodParametersAttribute(ref reader, out attribute),
+ "Module" => ModuleAttributeRecord.TryReadModuleAttribute(ref reader, out attribute),
+ "ModulePackages" => ModulePackagesAttributeRecord.TryReadModulePackagesAttribute(ref reader, out attribute),
+ "ModuleMainClass" => ModuleMainClassAttributeRecord.TryReadModuleMainClassAttribute(ref reader, out attribute),
+ "NestHost" => NestHostAttributeRecord.TryReadNestHostAttribute(ref reader, out attribute),
+ "NestMembers" => NestMembersAttributeRecord.TryReadNestMembersAttribute(ref reader, out attribute),
+ "Record" => RecordAttributeRecord.TryReadRecordAttribute(ref reader, out attribute),
+ "PermittedSubclasses" => PermittedSubclassesAttributeRecord.TryReadPermittedSubclassesAttribute(ref reader, out attribute),
+ _ => UnknownAttributeRecord.TryReadCustomAttribute(ref reader, out attribute),
+ };
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeMethodRecord.cs b/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeMethodRecord.cs
new file mode 100644
index 0000000000..b40529a97b
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeMethodRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct BootstrapMethodsAttributeMethodRecord(ushort MethodRefIndex, ushort[] Arguments)
+ {
+
+ public static bool TryReadBootstrapMethod(ref ClassFormatReader reader, out BootstrapMethodsAttributeMethodRecord method)
+ {
+ method = default;
+
+ if (reader.TryReadU2(out ushort methodRefIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort argumentCount) == false)
+ return false;
+
+ var arguments = new ushort[argumentCount];
+ for (int i = 0; i < argumentCount; i++)
+ {
+ if (reader.TryReadU2(out ushort argumentIndex) == false)
+ return false;
+
+ arguments[i] = argumentIndex;
+ }
+
+ method = new BootstrapMethodsAttributeMethodRecord(methodRefIndex, arguments);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeRecord.cs
new file mode 100644
index 0000000000..5fa5c4e554
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/BootstrapMethodsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record BootstrapMethodsAttributeRecord(BootstrapMethodsAttributeMethodRecord[] Methods) : AttributeRecord
+ {
+
+ public static bool TryReadBootstrapMethodsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var methods = new BootstrapMethodsAttributeMethodRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (BootstrapMethodsAttributeMethodRecord.TryReadBootstrapMethod(ref reader, out var method) == false)
+ return false;
+
+ methods[i] = method;
+ }
+
+ attribute = new BootstrapMethodsAttributeRecord(methods);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ChopStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/ChopStackMapFrameRecord.cs
new file mode 100644
index 0000000000..cd7a52c5a6
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ChopStackMapFrameRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ChopStackMapFrameRecord(byte Tag, ushort OffsetDelta) : StackMapFrameRecord(Tag)
+ {
+
+ public static bool TryReadChopStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU2(out ushort offsetDelta) == false)
+ return false;
+
+ frame = new ChopStackMapFrameRecord(tag, offsetDelta);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ClassConstantRecord.cs b/src/IKVM.ByteCode/Parsing/ClassConstantRecord.cs
new file mode 100644
index 0000000000..67502749e5
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ClassConstantRecord.cs
@@ -0,0 +1,26 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ClassConstantRecord(ushort NameIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Class constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadClassConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+
+ constant = new ClassConstantRecord(nameIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ClassFormatReader.cs b/src/IKVM.ByteCode/Parsing/ClassFormatReader.cs
new file mode 100644
index 0000000000..443eafddbe
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ClassFormatReader.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Buffers;
+
+using IKVM.ByteCode.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ ///
+ /// Provides common methods to read through memory of a Class file.
+ ///
+ internal ref struct ClassFormatReader
+ {
+
+ SequenceReader reader;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ public ClassFormatReader(ReadOnlyMemory buffer) :
+ this(new ReadOnlySequence(buffer))
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ public ClassFormatReader(ReadOnlySequence sequence)
+ {
+ reader = new SequenceReader(sequence);
+ }
+
+ ///
+ /// Gets the count of bytes in the reader.
+ ///
+ public long Length => reader.Length;
+
+ ///
+ /// Attempts to read a value defined as a 'u1'.
+ ///
+ ///
+ ///
+ public bool TryReadU1(out byte u1)
+ {
+ return reader.TryRead(out u1);
+ }
+
+ ///
+ /// Attempts to read a value defined as a 'u2'.
+ ///
+ ///
+ ///
+ public bool TryReadU2(out ushort u2)
+ {
+ return reader.TryReadBigEndian(out u2);
+ }
+
+ ///
+ /// Attempts to read a value defined as a 'u4'.
+ ///
+ ///
+ ///
+ public bool TryReadU4(out uint u4)
+ {
+ return reader.TryReadBigEndian(out u4);
+ }
+
+ ///
+ /// Attempts to read the exact given number of bytes.
+ ///
+ ///
+ ///
+ ///
+ public bool TryReadManyU1(uint count, out ReadOnlySequence sequence)
+ {
+ return reader.TryReadExact(count, out sequence);
+ }
+
+ ///
+ /// Attempts to read the exact given number of bytes.
+ ///
+ ///
+ ///
+ ///
+ public bool TryReadManyU1(long count, out ReadOnlySequence sequence)
+ {
+ return reader.TryReadExact(count, out sequence);
+ }
+
+ ///
+ /// Attempts to copy available data at the current position to the destination.
+ ///
+ ///
+ ///
+ public bool TryCopyTo(Span destination)
+ {
+ return reader.TryCopyTo(destination);
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ClassFormatWriter.cs b/src/IKVM.ByteCode/Parsing/ClassFormatWriter.cs
new file mode 100644
index 0000000000..eba9ddc1fe
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ClassFormatWriter.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Buffers.Binary;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ ///
+ /// Provides a forward-only writer of big-endian values.
+ ///
+ internal ref struct ClassFormatWriter
+ {
+
+ Span span;
+ Span next;
+ long size = 0;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ public ClassFormatWriter(Span span)
+ {
+ this.span = next = span;
+ }
+
+ ///
+ /// Gets the span being written to.
+ ///
+ public Span Span => span;
+
+ ///
+ /// Gets the total number of written bytes.
+ ///
+ public long Size => size;
+
+ ///
+ /// Writes a value defined as a 'u1' in the class format specification.
+ ///
+ ///
+ ///
+ public bool TryWriteU1(byte value)
+ {
+ if (next.Length < sizeof(byte))
+ return false;
+
+ next[0] = value;
+ next = next.Slice(sizeof(byte));
+ size += sizeof(byte);
+ return true;
+ }
+
+ ///
+ /// Writes a value defined as a 'u2' in the class format specification.
+ ///
+ ///
+ ///
+ public bool TryWriteU2(ushort value)
+ {
+ if (next.Length < sizeof(ushort))
+ return false;
+
+ BinaryPrimitives.WriteUInt16BigEndian(next, value);
+ next = next.Slice(sizeof(ushort));
+ size += sizeof(ushort);
+ return true;
+ }
+
+ ///
+ /// Writes a value defined as a 'u4' in the class format specification.
+ ///
+ ///
+ ///
+ public bool TryWriteU4(uint value)
+ {
+ if (next.Length < sizeof(uint))
+ return false;
+
+ BinaryPrimitives.WriteUInt32BigEndian(next, value);
+ next = next.Slice(sizeof(uint));
+ size += sizeof(uint);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ClassRecord.cs b/src/IKVM.ByteCode/Parsing/ClassRecord.cs
new file mode 100644
index 0000000000..577cf28515
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ClassRecord.cs
@@ -0,0 +1,187 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ClassRecord(ushort MinorVersion, ushort MajorVersion, ConstantRecord[] Constants, AccessFlag AccessFlags, ushort ThisClassIndex, ushort SuperClassIndex, InterfaceRecord[] Interfaces, FieldRecord[] Fields, MethodRecord[] Methods, AttributeInfoRecord[] Attributes)
+ {
+
+ const uint MAGIC = 0xCAFEBABE;
+
+ ///
+ /// Attempts to read a class record starting at the current position.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out ClassRecord clazz)
+ {
+ clazz = default;
+
+ if (reader.TryReadU4(out uint magic) == false)
+ return false;
+ if (magic != MAGIC)
+ throw new ByteCodeException($"Unexpected magic value '{magic}'.");
+
+ if (reader.TryReadU2(out ushort minorVersion) == false)
+ return false;
+ if (reader.TryReadU2(out ushort majorVersion) == false)
+ return false;
+
+ if (majorVersion > 63)
+ throw new UnsupportedClassVersionException(new ClassFormatVersion(majorVersion, minorVersion));
+
+ if (TryReadConstants(ref reader, out var constants) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort accessFlags) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort thisClass) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort superClass) == false)
+ return false;
+
+ if (TryReadInterfaces(ref reader, out var interfaces) == false)
+ return false;
+
+ if (TryReadFields(ref reader, out var fields) == false)
+ return false;
+
+ if (TryReadMethods(ref reader, out var methods) == false)
+ return false;
+
+ if (TryReadAttributes(ref reader, out var attributes) == false)
+ return false;
+
+ clazz = new ClassRecord(minorVersion, majorVersion, constants, (AccessFlag)accessFlags, thisClass, superClass, interfaces, fields, methods, attributes);
+ return true;
+ }
+
+ ///
+ /// Attempts to read the set of constants at the current position.
+ ///
+ ///
+ ///
+ ///
+ static bool TryReadConstants(ref ClassFormatReader reader, out ConstantRecord[] constants)
+ {
+ constants = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ constants = new ConstantRecord[count];
+ for (int i = 1; i < count; i++)
+ {
+ if (ConstantRecord.TryRead(ref reader, out var constant, out var skip) == false)
+ return false;
+
+ constants[i] = constant;
+ i += skip;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to read the set of interfaces starting from the current position.
+ ///
+ ///
+ ///
+ ///
+ static bool TryReadInterfaces(ref ClassFormatReader reader, out InterfaceRecord[] interfaces)
+ {
+ interfaces = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ interfaces = new InterfaceRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (InterfaceRecord.TryRead(ref reader, out InterfaceRecord iface) == false)
+ return false;
+
+ interfaces[i] = iface;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to read the set of fields starting from the current position.
+ ///
+ ///
+ ///
+ ///
+ static bool TryReadFields(ref ClassFormatReader reader, out FieldRecord[] fields)
+ {
+ fields = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ fields = new FieldRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (FieldRecord.TryRead(ref reader, out FieldRecord field) == false)
+ return false;
+
+ fields[i] = field;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Attempts to read the set of methods starting from the current position.
+ ///
+ ///
+ ///
+ ///
+ static bool TryReadMethods(ref ClassFormatReader reader, out MethodRecord[] methods)
+ {
+ methods = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ methods = new MethodRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (MethodRecord.TryRead(ref reader, out MethodRecord method) == false)
+ return false;
+
+ methods[i] = method;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Parses an attributes count followed by a sequence of attributes.
+ ///
+ ///
+ ///
+ internal static bool TryReadAttributes(ref ClassFormatReader reader, out AttributeInfoRecord[] attributes)
+ {
+ attributes = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ attributes = new AttributeInfoRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (AttributeInfoRecord.TryReadAttribute(ref reader, out var attribute) == false)
+ return false;
+
+ attributes[i] = attribute;
+ }
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/CodeAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/CodeAttributeRecord.cs
new file mode 100644
index 0000000000..e8bd677a31
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/CodeAttributeRecord.cs
@@ -0,0 +1,53 @@
+using System;
+using System.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record CodeAttributeRecord(ushort MaxStack, ushort MaxLocals, ReadOnlyMemory Code, ExceptionHandlerRecord[] ExceptionTable, AttributeInfoRecord[] Attributes) : AttributeRecord
+ {
+
+ public static bool TryReadCodeAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort maxStack) == false)
+ return false;
+ if (reader.TryReadU2(out ushort maxLocals) == false)
+ return false;
+ if (reader.TryReadU4(out uint codeLength) == false)
+ return false;
+ if (reader.TryReadManyU1(codeLength, out ReadOnlySequence code) == false)
+ return false;
+
+ var codeBuffer = new byte[code.Length];
+ code.CopyTo(codeBuffer);
+
+ if (reader.TryReadU2(out ushort exceptionTableLength) == false)
+ return false;
+
+ var exceptionTable = new ExceptionHandlerRecord[(int)exceptionTableLength];
+ for (int i = 0; i < exceptionTableLength; i++)
+ {
+ if (reader.TryReadU2(out ushort startOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort endOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort handlerOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort catchTypeIndex) == false)
+ return false;
+
+ exceptionTable[i] = new ExceptionHandlerRecord(startOffset, endOffset, handlerOffset, catchTypeIndex);
+ }
+
+ if (ClassRecord.TryReadAttributes(ref reader, out var attributes) == false)
+ return false;
+
+ attribute = new CodeAttributeRecord(maxStack, maxLocals, codeBuffer, exceptionTable, attributes);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ConstantRecord.cs b/src/IKVM.ByteCode/Parsing/ConstantRecord.cs
new file mode 100644
index 0000000000..3986e4cb61
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ConstantRecord.cs
@@ -0,0 +1,50 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ ///
+ /// Base type for constant records.
+ ///
+ internal abstract record ConstantRecord
+ {
+
+ ///
+ /// Attempts to read the constant at the current position. Returns the the number of index positions to skip.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU1(out byte tag) == false)
+ return false;
+
+ return (ConstantTag)tag switch
+ {
+ ConstantTag.Utf8 => Utf8ConstantRecord.TryReadUtf8Constant(ref reader, out constant, out skip),
+ ConstantTag.Integer => IntegerConstantRecord.TryReadIntegerConstant(ref reader, out constant, out skip),
+ ConstantTag.Float => FloatConstantRecord.TryReadFloatConstant(ref reader, out constant, out skip),
+ ConstantTag.Long => LongConstantRecord.TryReadLongConstant(ref reader, out constant, out skip),
+ ConstantTag.Double => DoubleConstantRecord.TryReadDoubleConstant(ref reader, out constant, out skip),
+ ConstantTag.Class => ClassConstantRecord.TryReadClassConstant(ref reader, out constant, out skip),
+ ConstantTag.String => StringConstantRecord.TryReadStringConstant(ref reader, out constant, out skip),
+ ConstantTag.Fieldref => FieldrefConstantRecord.TryReadFieldrefConstant(ref reader, out constant, out skip),
+ ConstantTag.Methodref => MethodrefConstantRecord.TryReadMethodrefConstant(ref reader, out constant, out skip),
+ ConstantTag.InterfaceMethodref => InterfaceMethodrefConstantRecord.TryReadInterfaceMethodrefConstant(ref reader, out constant, out skip),
+ ConstantTag.NameAndType => NameAndTypeConstantRecord.TryReadNameAndTypeConstant(ref reader, out constant, out skip),
+ ConstantTag.MethodHandle => MethodHandleConstantRecord.TryReadMethodHandleConstant(ref reader, out constant, out skip),
+ ConstantTag.MethodType => MethodTypeConstantRecord.TryReadMethodTypeConstant(ref reader, out constant, out skip),
+ ConstantTag.Dynamic => DynamicConstantRecord.TryReadDynamicConstant(ref reader, out constant, out skip),
+ ConstantTag.InvokeDynamic => InvokeDynamicConstantRecord.TryReadInvokeDynamicConstant(ref reader, out constant, out skip),
+ ConstantTag.Module => ModuleConstantRecord.TryReadModuleConstant(ref reader, out constant, out skip),
+ ConstantTag.Package => PackageConstantRecord.TryReadPackageConstant(ref reader, out constant, out skip),
+ _ => throw new ByteCodeException($"Encountered an unknown constant tag: '0x{tag:X}'."),
+ };
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ConstantTag.cs b/src/IKVM.ByteCode/Parsing/ConstantTag.cs
new file mode 100644
index 0000000000..94a712c1d3
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ConstantTag.cs
@@ -0,0 +1,27 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal enum ConstantTag : byte
+ {
+
+ Utf8 = 1,
+ Integer = 3,
+ Float = 4,
+ Long = 5,
+ Double = 6,
+ Class = 7,
+ String = 8,
+ Fieldref = 9,
+ Methodref = 10,
+ InterfaceMethodref = 11,
+ NameAndType = 12,
+ MethodHandle = 15,
+ MethodType = 16,
+ Dynamic = 17,
+ InvokeDynamic = 18,
+ Module = 19,
+ Package = 20,
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ConstantValueAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/ConstantValueAttributeRecord.cs
new file mode 100644
index 0000000000..1859a13d23
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ConstantValueAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ConstantValueAttributeRecord(ushort ValueIndex) : AttributeRecord
+ {
+
+ public static bool TryReadConstantValueAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort valueIndex) == false)
+ return false;
+
+ attribute = new ConstantValueAttributeRecord(valueIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/DeprecatedAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/DeprecatedAttributeRecord.cs
new file mode 100644
index 0000000000..eb835d2106
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/DeprecatedAttributeRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record DeprecatedAttributeRecord : AttributeRecord
+ {
+
+ public static bool TryReadDeprecatedAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = new DeprecatedAttributeRecord();
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/DoubleConstantRecord.cs b/src/IKVM.ByteCode/Parsing/DoubleConstantRecord.cs
new file mode 100644
index 0000000000..420e4564eb
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/DoubleConstantRecord.cs
@@ -0,0 +1,38 @@
+using System;
+
+using IKVM.ByteCode.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record DoubleConstantRecord(double Value) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Double constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadDoubleConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 1;
+
+ if (reader.TryReadU4(out uint a) == false)
+ return false;
+ if (reader.TryReadU4(out uint b) == false)
+ return false;
+
+#if NETFRAMEWORK || NETCOREAPP3_1
+ var v = RawBitConverter.UInt64BitsToDouble(((ulong)a << 32) | b);
+#else
+ var v = BitConverter.UInt64BitsToDouble(((ulong)a << 32) | b);
+#endif
+
+ constant = new DoubleConstantRecord(v);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/DoubleVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/DoubleVariableInfoRecord.cs
new file mode 100644
index 0000000000..3363bcad60
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/DoubleVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record DoubleVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new DoubleVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/DynamicConstantRecord.cs b/src/IKVM.ByteCode/Parsing/DynamicConstantRecord.cs
new file mode 100644
index 0000000000..c5e003142e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/DynamicConstantRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record DynamicConstantRecord(ushort BootstrapMethodAttributeIndex, ushort NameAndTypeIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Dynamic constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadDynamicConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skipIndex)
+ {
+ constant = null;
+ skipIndex = 0;
+
+ if (reader.TryReadU2(out ushort bootstrapMethodAttrIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameAndTypeIndex) == false)
+ return false;
+
+ constant = new DynamicConstantRecord(bootstrapMethodAttrIndex, nameAndTypeIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueAnnotationValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueAnnotationValueRecord.cs
new file mode 100644
index 0000000000..229f263559
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueAnnotationValueRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ElementValueAnnotationValueRecord(AnnotationRecord Annotation) : ElementValueValueRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueValueRecord value)
+ {
+ value = null;
+
+ if (AnnotationRecord.TryReadAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ value = new ElementValueAnnotationValueRecord(annotation);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var size = 0;
+ size += Annotation.GetSize();
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (Annotation.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueArrayValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueArrayValueRecord.cs
new file mode 100644
index 0000000000..d1c6b664c9
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueArrayValueRecord.cs
@@ -0,0 +1,53 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ElementValueArrayValueRecord(ElementValueRecord[] Values) : ElementValueValueRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueValueRecord value)
+ {
+ value = null;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+
+ var values = new ElementValueRecord[length];
+ for (int i = 0; i < length; i++)
+ {
+ if (ElementValueRecord.TryRead(ref reader, out var j) == false)
+ return false;
+
+ values[i] = j;
+ }
+
+ value = new ElementValueArrayValueRecord(values);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+
+ foreach (var value in Values)
+ size += value.GetSize();
+
+ return size;
+ }
+
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2((ushort)Values.Length) == false)
+ return false;
+
+ foreach (var value in Values)
+ if (value.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueClassValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueClassValueRecord.cs
new file mode 100644
index 0000000000..8267ae8a46
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueClassValueRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ElementValueClassValueRecord(ushort ClassIndex) : ElementValueValueRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueValueRecord value)
+ {
+ value = null;
+
+ if (reader.TryReadU2(out ushort classInfoIndex) == false)
+ return false;
+
+ value = new ElementValueClassValueRecord(classInfoIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(ClassIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueConstantValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueConstantValueRecord.cs
new file mode 100644
index 0000000000..6d791c6d00
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueConstantValueRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ElementValueConstantValueRecord(ushort Index) : ElementValueValueRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueValueRecord value)
+ {
+ value = null;
+
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ value = new ElementValueConstantValueRecord(index);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(Index) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueEnumConstantValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueEnumConstantValueRecord.cs
new file mode 100644
index 0000000000..15b3717f24
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueEnumConstantValueRecord.cs
@@ -0,0 +1,45 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ElementValueEnumConstantValueRecord(ushort TypeNameIndex, ushort ConstantNameIndex) : ElementValueValueRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueValueRecord value)
+ {
+ value = null;
+
+ if (reader.TryReadU2(out ushort typeNameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort constantNameIndex) == false)
+ return false;
+
+ value = new ElementValueEnumConstantValueRecord(typeNameIndex, constantNameIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+ size += sizeof(ushort);
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(TypeNameIndex) == false)
+ return false;
+ if (writer.TryWriteU2(ConstantNameIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValuePairRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValuePairRecord.cs
new file mode 100644
index 0000000000..f94ed0427a
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValuePairRecord.cs
@@ -0,0 +1,45 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ElementValuePairRecord(ushort NameIndex, ElementValueRecord Value)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValuePairRecord record)
+ {
+ record = default;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (ElementValueRecord.TryRead(ref reader, out var elementValue) == false)
+ return false;
+
+ record = new ElementValuePairRecord(nameIndex, elementValue);
+ return true;
+ }
+
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+ size += Value.GetSize();
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(NameIndex) == false)
+ return false;
+ if (Value.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueRecord.cs
new file mode 100644
index 0000000000..4014a0f920
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueRecord.cs
@@ -0,0 +1,64 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ElementValueRecord(ElementValueTag Tag, ElementValueValueRecord Value)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out ElementValueRecord record)
+ {
+ record = default;
+
+ if (reader.TryReadU1(out byte tag) == false)
+ return false;
+ if (TryReadValue(ref reader, (ElementValueTag)tag, out var value) == false)
+ return false;
+
+ record = new ElementValueRecord((ElementValueTag)tag, value);
+ return true;
+ }
+
+ static bool TryReadValue(ref ClassFormatReader reader, ElementValueTag tag, out ElementValueValueRecord value) => tag switch
+ {
+ ElementValueTag.Byte => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Char => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Double => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Float => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Integer => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Long => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Short => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Boolean => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.String => ElementValueConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Enum => ElementValueEnumConstantValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Class => ElementValueClassValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Annotation => ElementValueAnnotationValueRecord.TryRead(ref reader, out value),
+ ElementValueTag.Array => ElementValueArrayValueRecord.TryRead(ref reader, out value),
+ _ => throw new ByteCodeException($"Invalid element value tag: '{(char)tag}'."),
+ };
+
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(byte);
+ size += Value.GetSize();
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1((byte)Tag) == false)
+ return false;
+
+ if (Value.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueTag.cs b/src/IKVM.ByteCode/Parsing/ElementValueTag.cs
new file mode 100644
index 0000000000..76e388b5e6
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueTag.cs
@@ -0,0 +1,23 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal enum ElementValueTag : byte
+ {
+
+ Byte = (byte)'B',
+ Char = (byte)'C',
+ Double = (byte)'D',
+ Float = (byte)'F',
+ Integer = (byte)'I',
+ Long = (byte)'J',
+ Short = (byte)'S',
+ Boolean = (byte)'Z',
+ String = (byte)'s',
+ Enum = (byte)'e',
+ Class = (byte)'c',
+ Annotation = (byte)'@',
+ Array = (byte)'[',
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ElementValueValueRecord.cs b/src/IKVM.ByteCode/Parsing/ElementValueValueRecord.cs
new file mode 100644
index 0000000000..e1d51b57c1
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ElementValueValueRecord.cs
@@ -0,0 +1,22 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record ElementValueValueRecord
+ {
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public abstract int GetSize();
+
+ ///
+ /// Attempts to write the record to the .
+ ///
+ ///
+ ///
+ public abstract bool TryWrite(ref ClassFormatWriter writer);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/EnclosingMethodAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/EnclosingMethodAttributeRecord.cs
new file mode 100644
index 0000000000..0d19202ea7
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/EnclosingMethodAttributeRecord.cs
@@ -0,0 +1,22 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record EnclosingMethodAttributeRecord(ushort ClassIndex, ushort MethodIndex) : AttributeRecord
+ {
+
+ public static bool TryReadEnclosingMethodAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort methodIndex) == false)
+ return false;
+
+ attribute = new EnclosingMethodAttributeRecord(classIndex, methodIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ExceptionHandlerRecord.cs b/src/IKVM.ByteCode/Parsing/ExceptionHandlerRecord.cs
new file mode 100644
index 0000000000..e17b4ef97f
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ExceptionHandlerRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ExceptionHandlerRecord(ushort StartOffset, ushort EndOffset, ushort HandlerOffset, ushort CatchTypeIndex);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ExceptionsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/ExceptionsAttributeRecord.cs
new file mode 100644
index 0000000000..e000b4f5ec
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ExceptionsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ExceptionsAttributeRecord(ushort[] ExceptionsIndexes) : AttributeRecord
+ {
+
+ public static bool TryReadExceptionsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var entries = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ entries[i] = index;
+ }
+
+ attribute = new ExceptionsAttributeRecord(entries);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/FieldRecord.cs b/src/IKVM.ByteCode/Parsing/FieldRecord.cs
new file mode 100644
index 0000000000..5471982603
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/FieldRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct FieldRecord(AccessFlag AccessFlags, ushort NameIndex, ushort DescriptorIndex, AttributeInfoRecord[] Attributes)
+ {
+
+ ///
+ /// Parses a field.
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out FieldRecord field)
+ {
+ field = default;
+
+ if (reader.TryReadU2(out ushort accessFlags) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+ if (ClassRecord.TryReadAttributes(ref reader, out var attributes) == false)
+ return false;
+
+ field = new FieldRecord((AccessFlag)accessFlags, nameIndex, descriptorIndex, attributes);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/FieldrefConstantRecord.cs b/src/IKVM.ByteCode/Parsing/FieldrefConstantRecord.cs
new file mode 100644
index 0000000000..da7a9c03b6
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/FieldrefConstantRecord.cs
@@ -0,0 +1,28 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record FieldrefConstantRecord(ushort ClassIndex, ushort NameAndTypeIndex) : RefConstantRecord(ClassIndex, NameAndTypeIndex)
+ {
+
+ ///
+ /// Parses a Fieldref constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadFieldrefConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameAndTypeIndex) == false)
+ return false;
+
+ constant = new FieldrefConstantRecord(classIndex, nameAndTypeIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/FloatConstantRecord.cs b/src/IKVM.ByteCode/Parsing/FloatConstantRecord.cs
new file mode 100644
index 0000000000..7078cff4ef
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/FloatConstantRecord.cs
@@ -0,0 +1,36 @@
+using System;
+
+using IKVM.ByteCode.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record FloatConstantRecord(float Value) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Float constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadFloatConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU4(out uint value) == false)
+ return false;
+
+#if NETFRAMEWORK || NETCOREAPP3_1
+ var v = RawBitConverter.UInt32BitsToSingle(value);
+#else
+ var v = BitConverter.UInt32BitsToSingle(value);
+#endif
+
+ constant = new FloatConstantRecord(v);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/FloatVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/FloatVariableInfoRecord.cs
new file mode 100644
index 0000000000..538ab9bf7e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/FloatVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record FloatVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new FloatVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/FullStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/FullStackMapFrameRecord.cs
new file mode 100644
index 0000000000..0a933c5247
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/FullStackMapFrameRecord.cs
@@ -0,0 +1,36 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record FullStackMapFrameRecord(byte Tag, ushort OffsetDelta, VerificationTypeInfoRecord[] Locals, VerificationTypeInfoRecord[] Stack) : StackMapFrameRecord(Tag)
+ {
+
+ public static bool TryReadFullStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU2(out ushort offsetDelta) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort localsCount) == false)
+ return false;
+
+ var locals = new VerificationTypeInfoRecord[localsCount];
+ for (int i = 0; i < localsCount; i++)
+ if (VerificationTypeInfoRecord.TryReadVerificationTypeInfo(ref reader, out locals[i]) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort stackCount) == false)
+ return false;
+
+ var stack = new VerificationTypeInfoRecord[stackCount];
+ for (int i = 0; i < stackCount; i++)
+ if (VerificationTypeInfoRecord.TryReadVerificationTypeInfo(ref reader, out stack[i]) == false)
+ return false;
+
+ frame = new FullStackMapFrameRecord(tag, offsetDelta, locals, stack);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/InnerClassesAttributeItemRecord.cs b/src/IKVM.ByteCode/Parsing/InnerClassesAttributeItemRecord.cs
new file mode 100644
index 0000000000..03d245a23e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/InnerClassesAttributeItemRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct InnerClassesAttributeItemRecord(ushort InnerClassIndex, ushort OuterClassIndex, ushort InnerNameIndex, AccessFlag InnerClassAccessFlags);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/InnerClassesAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/InnerClassesAttributeRecord.cs
new file mode 100644
index 0000000000..eec2f9ee6c
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/InnerClassesAttributeRecord.cs
@@ -0,0 +1,35 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record InnerClassesAttributeRecord(InnerClassesAttributeItemRecord[] Items) : AttributeRecord
+ {
+
+ public static bool TryReadInnerClassesAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var items = new InnerClassesAttributeItemRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort innerClassInfoIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort outerClassInfoIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort innerNameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort innerClassAccessFlags) == false)
+ return false;
+
+ items[i] = new InnerClassesAttributeItemRecord(innerClassInfoIndex, outerClassInfoIndex, innerNameIndex, (AccessFlag)innerClassAccessFlags);
+ }
+
+ attribute = new InnerClassesAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/IntegerConstantRecord.cs b/src/IKVM.ByteCode/Parsing/IntegerConstantRecord.cs
new file mode 100644
index 0000000000..1f07772f39
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/IntegerConstantRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record IntegerConstantRecord(int Value) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Integer constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadIntegerConstant(ref ClassFormatReader reader, out ConstantRecord record, out int skip)
+ {
+ record = null;
+ skip = 0;
+
+ if (reader.TryReadU4(out uint value) == false)
+ return false;
+
+ var v = unchecked((int)value);
+
+ record = new IntegerConstantRecord(v);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/IntegerVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/IntegerVariableInfoRecord.cs
new file mode 100644
index 0000000000..978c79e69d
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/IntegerVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record IntegerVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new IntegerVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/InterfaceMethodrefConstantRecord.cs b/src/IKVM.ByteCode/Parsing/InterfaceMethodrefConstantRecord.cs
new file mode 100644
index 0000000000..8a4f8df66a
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/InterfaceMethodrefConstantRecord.cs
@@ -0,0 +1,28 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record InterfaceMethodrefConstantRecord(ushort ClassIndex, ushort NameAndTypeIndex) : RefConstantRecord(ClassIndex, NameAndTypeIndex)
+ {
+
+ ///
+ /// Parses a InterfaceMethodref constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadInterfaceMethodrefConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameAndTypeIndex) == false)
+ return false;
+
+ constant = new InterfaceMethodrefConstantRecord(classIndex, nameAndTypeIndex);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/InterfaceRecord.cs b/src/IKVM.ByteCode/Parsing/InterfaceRecord.cs
new file mode 100644
index 0000000000..6044da7dd1
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/InterfaceRecord.cs
@@ -0,0 +1,25 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct InterfaceRecord(ushort ClassIndex)
+ {
+
+ ///
+ /// Parses an interface.
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out InterfaceRecord iface)
+ {
+ iface = default;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+
+ iface = new InterfaceRecord(classIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/InvokeDynamicConstantRecord.cs b/src/IKVM.ByteCode/Parsing/InvokeDynamicConstantRecord.cs
new file mode 100644
index 0000000000..fe82591c34
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/InvokeDynamicConstantRecord.cs
@@ -0,0 +1,28 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record InvokeDynamicConstantRecord(ushort BootstrapMethodAttributeIndex, ushort NameAndTypeIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a InvokeDynamic constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadInvokeDynamicConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort bootstrapMethodAttrIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameAndTypeIndex) == false)
+ return false;
+
+ constant = new InvokeDynamicConstantRecord(bootstrapMethodAttrIndex, nameAndTypeIndex);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeItemRecord.cs b/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeItemRecord.cs
new file mode 100644
index 0000000000..cba1871c9f
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeItemRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct LineNumberTableAttributeItemRecord(ushort CodeOffset, ushort LineNumber);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeRecord.cs
new file mode 100644
index 0000000000..b4ef48d0c0
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LineNumberTableAttributeRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record LineNumberTableAttributeRecord(LineNumberTableAttributeItemRecord[] Items) : AttributeRecord
+ {
+
+ public static bool TryReadLineNumberTableAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort itemCount) == false)
+ return false;
+
+ var items = new LineNumberTableAttributeItemRecord[itemCount];
+ for (int i = 0; i < itemCount; i++)
+ {
+ if (reader.TryReadU2(out ushort codeOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort lineNumber) == false)
+ return false;
+
+ items[i] = new LineNumberTableAttributeItemRecord(codeOffset, lineNumber);
+ }
+
+ attribute = new LineNumberTableAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeItemRecord.cs b/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeItemRecord.cs
new file mode 100644
index 0000000000..38ffc6a6ee
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeItemRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct LocalVariableTableAttributeItemRecord(ushort CodeOffset, ushort CodeLength, ushort NameIndex, ushort DescriptorIndex, ushort Index);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeRecord.cs
new file mode 100644
index 0000000000..3d0ab69e7e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LocalVariableTableAttributeRecord.cs
@@ -0,0 +1,37 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record LocalVariableTableAttributeRecord(LocalVariableTableAttributeItemRecord[] Items) : AttributeRecord
+ {
+
+ public static bool TryReadLocalVariableTableAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+
+ var items = new LocalVariableTableAttributeItemRecord[length];
+ for (int i = 0; i < length; i++)
+ {
+ if (reader.TryReadU2(out ushort codeOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort codeLength) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ items[i] = new LocalVariableTableAttributeItemRecord(codeOffset, codeLength, nameIndex, descriptorIndex, index);
+ }
+
+ attribute = new LocalVariableTableAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeItemRecord.cs b/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeItemRecord.cs
new file mode 100644
index 0000000000..7acdf2c994
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeItemRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct LocalVariableTypeTableAttributeItemRecord(ushort CodeOffset, ushort CodeLength, ushort NameIndex, ushort SignatureIndex, ushort Index);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeRecord.cs
new file mode 100644
index 0000000000..c1c8108f7e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LocalVariableTypeTableAttributeRecord.cs
@@ -0,0 +1,37 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record LocalVariableTypeTableAttributeRecord(LocalVariableTypeTableAttributeItemRecord[] Items) : AttributeRecord
+ {
+
+ public static bool TryReadLocalVariableTypeTableAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+
+ var items = new LocalVariableTypeTableAttributeItemRecord[length];
+ for (int i = 0; i < length; i++)
+ {
+ if (reader.TryReadU2(out ushort codeOffset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort codeLength) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort signatureIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ items[i] = new LocalVariableTypeTableAttributeItemRecord(codeOffset, codeLength, nameIndex, signatureIndex, index);
+ }
+
+ attribute = new LocalVariableTypeTableAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LongConstantRecord.cs b/src/IKVM.ByteCode/Parsing/LongConstantRecord.cs
new file mode 100644
index 0000000000..f20548c10b
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LongConstantRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record LongConstantRecord(long Value) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Long constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadLongConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 1;
+
+ if (reader.TryReadU4(out uint a) == false)
+ return false;
+ if (reader.TryReadU4(out uint b) == false)
+ return false;
+
+ constant = new LongConstantRecord((long)(((ulong)a << 32) | b));
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/LongVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/LongVariableInfoRecord.cs
new file mode 100644
index 0000000000..a37b596569
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/LongVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record LongVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new LongVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodHandleConstantRecord.cs b/src/IKVM.ByteCode/Parsing/MethodHandleConstantRecord.cs
new file mode 100644
index 0000000000..43e73a6ec3
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodHandleConstantRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record MethodHandleConstantRecord(ReferenceKind Kind, ushort Index) : ConstantRecord
+ {
+
+ ///
+ /// Parses a MethodHandle constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadMethodHandleConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU1(out byte kind) == false)
+ return false;
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ constant = new MethodHandleConstantRecord((ReferenceKind)kind, index);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodParametersAttributeParameterRecord.cs b/src/IKVM.ByteCode/Parsing/MethodParametersAttributeParameterRecord.cs
new file mode 100644
index 0000000000..508b687cdd
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodParametersAttributeParameterRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct MethodParametersAttributeParameterRecord(ushort NameIndex, AccessFlag AccessFlags);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodParametersAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/MethodParametersAttributeRecord.cs
new file mode 100644
index 0000000000..15e006ddef
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodParametersAttributeRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record MethodParametersAttributeRecord(MethodParametersAttributeParameterRecord[] Parameters) : AttributeRecord
+ {
+
+ public static bool TryReadMethodParametersAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU1(out byte count) == false)
+ return false;
+
+ var arguments = new MethodParametersAttributeParameterRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort accessFlags) == false)
+ return false;
+
+ arguments[i] = new MethodParametersAttributeParameterRecord(nameIndex, (AccessFlag)accessFlags);
+ }
+
+ attribute = new MethodParametersAttributeRecord(arguments);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodRecord.cs b/src/IKVM.ByteCode/Parsing/MethodRecord.cs
new file mode 100644
index 0000000000..96bdb9d3b5
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct MethodRecord(AccessFlag AccessFlags, ushort NameIndex, ushort DescriptorIndex, AttributeInfoRecord[] Attributes)
+ {
+
+ ///
+ /// Parses a method.
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out MethodRecord method)
+ {
+ method = default;
+
+ if (reader.TryReadU2(out ushort accessFlags) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+ if (ClassRecord.TryReadAttributes(ref reader, out var attributes) == false)
+ return false;
+
+ method = new MethodRecord((AccessFlag)accessFlags, nameIndex, descriptorIndex, attributes);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodTypeConstantRecord.cs b/src/IKVM.ByteCode/Parsing/MethodTypeConstantRecord.cs
new file mode 100644
index 0000000000..c8f2e38cc3
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodTypeConstantRecord.cs
@@ -0,0 +1,27 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record MethodTypeConstantRecord(ushort DescriptorIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a MethodType constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadMethodTypeConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+
+ constant = new MethodTypeConstantRecord(descriptorIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/MethodrefConstantRecord.cs b/src/IKVM.ByteCode/Parsing/MethodrefConstantRecord.cs
new file mode 100644
index 0000000000..41d1e08d5c
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/MethodrefConstantRecord.cs
@@ -0,0 +1,28 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record MethodrefConstantRecord(ushort ClassIndex, ushort NameAndTypeIndex) : RefConstantRecord(ClassIndex, NameAndTypeIndex)
+ {
+
+ ///
+ /// Parses a Methodref constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadMethodrefConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort nameAndTypeIndex) == false)
+ return false;
+
+ constant = new MethodrefConstantRecord(classIndex, nameAndTypeIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleAttributeExportsRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleAttributeExportsRecord.cs
new file mode 100644
index 0000000000..33f3803fd0
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleAttributeExportsRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ModuleAttributeExportsRecord(ushort Index, ModuleExportsFlag Flags, ushort[] Modules);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleAttributeOpensRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleAttributeOpensRecord.cs
new file mode 100644
index 0000000000..0bafd0d9cf
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleAttributeOpensRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ModuleAttributeOpensRecord(ushort Index, ModuleOpensFlag Flags, ushort[] Modules);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleAttributeProvidesRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleAttributeProvidesRecord.cs
new file mode 100644
index 0000000000..b7bd3adb70
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleAttributeProvidesRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ModuleAttributeProvidesRecord(ushort Index, ushort[] Modules);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleAttributeRecord.cs
new file mode 100644
index 0000000000..30689d1b84
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleAttributeRecord.cs
@@ -0,0 +1,128 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ModuleAttributeRecord(ushort NameIndex, ModuleFlag Flags, ushort VersionIndex, ModuleAttributeRequiresRecord[] Requires, ModuleAttributeExportsRecord[] Exports, ModuleAttributeOpensRecord[] Opens, ushort[] Uses, ModuleAttributeProvidesRecord[] Provides) : AttributeRecord
+ {
+
+ public static bool TryReadModuleAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort moduleNameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort moduleFlags) == false)
+ return false;
+ if (reader.TryReadU2(out ushort moduleVersionIndex) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort requiresCount) == false)
+ return false;
+
+ var requires = new ModuleAttributeRequiresRecord[requiresCount];
+ for (int i = 0; i < requiresCount; i++)
+ {
+ if (reader.TryReadU2(out ushort requiresIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort requiresFlags) == false)
+ return false;
+ if (reader.TryReadU2(out ushort requiresVersionIndex) == false)
+ return false;
+
+ requires[i] = new ModuleAttributeRequiresRecord(requiresIndex, (ModuleRequiresFlag)requiresFlags, requiresVersionIndex);
+ }
+
+ if (reader.TryReadU2(out ushort exportsCount) == false)
+ return false;
+
+ var exports = new ModuleAttributeExportsRecord[exportsCount];
+ for (int i = 0; i < exportsCount; i++)
+ {
+ if (reader.TryReadU2(out ushort exportsIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort exportsFlags) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort exportsModuleCount) == false)
+ return false;
+
+ var exportsModules = new ushort[exportsModuleCount];
+ for (int j = 0; j < exportsModuleCount; j++)
+ {
+ if (reader.TryReadU2(out ushort exportsToModuleIndex) == false)
+ return false;
+
+ exportsModules[j] = exportsToModuleIndex;
+ }
+
+ exports[i] = new ModuleAttributeExportsRecord(exportsIndex, (ModuleExportsFlag)exportsFlags, exportsModules);
+ }
+
+ if (reader.TryReadU2(out ushort opensCount) == false)
+ return false;
+
+ var opens = new ModuleAttributeOpensRecord[opensCount];
+ for (int i = 0; i < opensCount; i++)
+ {
+ if (reader.TryReadU2(out ushort opensIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort opensFlags) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort opensModulesCount) == false)
+ return false;
+
+ var opensModules = new ushort[opensModulesCount];
+ for (int j = 0; j < opensModulesCount; j++)
+ {
+ if (reader.TryReadU2(out ushort opensModuleIndex) == false)
+ return false;
+
+ opensModules[j] = opensModuleIndex;
+ }
+
+ opens[i] = new ModuleAttributeOpensRecord(opensIndex, (ModuleOpensFlag)opensFlags, opensModules);
+ }
+
+ if (reader.TryReadU2(out ushort usesCount) == false)
+ return false;
+
+ var uses = new ushort[usesCount];
+ for (int i = 0; i < usesCount; i++)
+ {
+ if (reader.TryReadU2(out ushort usesIndex) == false)
+ return false;
+
+ uses[i] = usesIndex;
+ }
+
+ if (reader.TryReadU2(out ushort providesCount) == false)
+ return false;
+
+ var provides = new ModuleAttributeProvidesRecord[providesCount];
+ for (int i = 0; i < providesCount; i++)
+ {
+ if (reader.TryReadU2(out ushort providesIndex) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort providesModulesCount) == false)
+ return false;
+
+ var providesModules = new ushort[providesModulesCount];
+ for (int j = 0; j < providesModulesCount; j++)
+ {
+ if (reader.TryReadU2(out ushort providesModuleIndex) == false)
+ return false;
+
+ providesModules[j] = providesModuleIndex;
+ }
+
+ provides[i] = new ModuleAttributeProvidesRecord(providesIndex, providesModules);
+ }
+
+ attribute = new ModuleAttributeRecord(moduleNameIndex, (ModuleFlag)moduleFlags, moduleVersionIndex, requires, exports, opens, uses, provides);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleAttributeRequiresRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleAttributeRequiresRecord.cs
new file mode 100644
index 0000000000..5aa439ad2b
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleAttributeRequiresRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ModuleAttributeRequiresRecord(ushort Index, ModuleRequiresFlag Flag, ushort VersionIndex);
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/ModuleConstantRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleConstantRecord.cs
new file mode 100644
index 0000000000..2a1651c5c3
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleConstantRecord.cs
@@ -0,0 +1,27 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ModuleConstantRecord(ushort NameIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Module constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadModuleConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+
+ constant = new ModuleConstantRecord(nameIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModuleMainClassAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/ModuleMainClassAttributeRecord.cs
new file mode 100644
index 0000000000..068ab4f8e9
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModuleMainClassAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ModuleMainClassAttributeRecord(ushort MainClassIndex) : AttributeRecord
+ {
+
+ public static bool TryReadModuleMainClassAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort mainClassIndex) == false)
+ return false;
+
+ attribute = new ModuleMainClassAttributeRecord(mainClassIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ModulePackagesAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/ModulePackagesAttributeRecord.cs
new file mode 100644
index 0000000000..14ccf63703
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ModulePackagesAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ModulePackagesAttributeRecord(ushort[] Packages) : AttributeRecord
+ {
+
+ public static bool TryReadModulePackagesAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var packages = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort packageIndex) == false)
+ return false;
+
+ packages[i] = packageIndex;
+ }
+
+ attribute = new ModulePackagesAttributeRecord(packages);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/NameAndTypeConstantRecord.cs b/src/IKVM.ByteCode/Parsing/NameAndTypeConstantRecord.cs
new file mode 100644
index 0000000000..4a591055c1
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/NameAndTypeConstantRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record NameAndTypeConstantRecord(ushort NameIndex, ushort DescriptorIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a NameAndType constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadNameAndTypeConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+
+ constant = new NameAndTypeConstantRecord(nameIndex, descriptorIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/NestHostAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/NestHostAttributeRecord.cs
new file mode 100644
index 0000000000..ff62be73c7
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/NestHostAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record NestHostAttributeRecord(ushort HostClassIndex) : AttributeRecord
+ {
+
+ public static bool TryReadNestHostAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort hostClassIndex) == false)
+ return false;
+
+ attribute = new NestHostAttributeRecord(hostClassIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/NestMembersAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/NestMembersAttributeRecord.cs
new file mode 100644
index 0000000000..b004fa52ad
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/NestMembersAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record NestMembersAttributeRecord(ushort[] ClassIndexes) : AttributeRecord
+ {
+
+ public static bool TryReadNestMembersAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var classes = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+
+ classes[i] = classIndex;
+ }
+
+ attribute = new NestMembersAttributeRecord(classes);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/NullVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/NullVariableInfoRecord.cs
new file mode 100644
index 0000000000..00c3b00c03
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/NullVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record NullVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new NullVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ObjectVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/ObjectVariableInfoRecord.cs
new file mode 100644
index 0000000000..e8d5667ea7
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ObjectVariableInfoRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record ObjectVariableInfoRecord(ushort ClassIndex) : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = null;
+
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+
+ record = new ObjectVariableInfoRecord(classIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/PackageConstantRecord.cs b/src/IKVM.ByteCode/Parsing/PackageConstantRecord.cs
new file mode 100644
index 0000000000..e98d5f2d75
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/PackageConstantRecord.cs
@@ -0,0 +1,27 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record PackageConstantRecord(ushort NameIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Package constant in the constant pool.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryReadPackageConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+
+ constant = new PackageConstantRecord(nameIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/ParameterAnnotationRecord.cs b/src/IKVM.ByteCode/Parsing/ParameterAnnotationRecord.cs
new file mode 100644
index 0000000000..6157f2bcde
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/ParameterAnnotationRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct ParameterAnnotationRecord(AnnotationRecord[] Annotations)
+ {
+
+ public static bool TryReadParameterAnnotation(ref ClassFormatReader reader, out ParameterAnnotationRecord record)
+ {
+ record = default;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var annotations = new AnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (AnnotationRecord.TryReadAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ annotations[i] = annotation;
+ }
+
+ record = new ParameterAnnotationRecord(annotations);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/PermittedSubclassesAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/PermittedSubclassesAttributeRecord.cs
new file mode 100644
index 0000000000..b1bd98046d
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/PermittedSubclassesAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record PermittedSubclassesAttributeRecord(ushort[] ClassIndexes) : AttributeRecord
+ {
+
+ public static bool TryReadPermittedSubclassesAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var classes = new ushort[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (reader.TryReadU2(out ushort classIndex) == false)
+ return false;
+
+ classes[i] = classIndex;
+ }
+
+ attribute = new PermittedSubclassesAttributeRecord(classes);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RecordAttributeComponentRecord.cs b/src/IKVM.ByteCode/Parsing/RecordAttributeComponentRecord.cs
new file mode 100644
index 0000000000..1fb3ff2162
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RecordAttributeComponentRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct RecordAttributeComponentRecord(ushort NameIndex, ushort DescriptorIndex, AttributeInfoRecord[] Attributes);
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RecordAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RecordAttributeRecord.cs
new file mode 100644
index 0000000000..192bc8ae94
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RecordAttributeRecord.cs
@@ -0,0 +1,33 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RecordAttributeRecord(RecordAttributeComponentRecord[] Components) : AttributeRecord
+ {
+
+ public static bool TryReadRecordAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort componentsCount) == false)
+ return false;
+
+ var components = new RecordAttributeComponentRecord[componentsCount];
+ for (int i = 0; i < componentsCount; i++)
+ {
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort descriptorIndex) == false)
+ return false;
+ if (ClassRecord.TryReadAttributes(ref reader, out var attributes) == false)
+ return false;
+
+ components[i] = new RecordAttributeComponentRecord(nameIndex, descriptorIndex, attributes);
+ }
+
+ attribute = new RecordAttributeRecord(components);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RefConstantRecord.cs b/src/IKVM.ByteCode/Parsing/RefConstantRecord.cs
new file mode 100644
index 0000000000..1df9453358
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RefConstantRecord.cs
@@ -0,0 +1,12 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record RefConstantRecord(ushort ClassIndex, ushort NameAndTypeIndex) :
+ ConstantRecord
+ {
+
+
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeInvisibleAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..7354beb0f1
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleAnnotationsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeInvisibleAnnotationsAttributeRecord(AnnotationRecord[] Annotations) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeInvisibleAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var items = new AnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (AnnotationRecord.TryReadAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ items[i] = annotation;
+ }
+
+ attribute = new RuntimeInvisibleAnnotationsAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeInvisibleParameterAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleParameterAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..77cac86d7e
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleParameterAnnotationsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeInvisibleParameterAnnotationsAttributeRecord(ParameterAnnotationRecord[] Parameters) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeInvisibleParameterAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU1(out byte count) == false)
+ return false;
+
+ var parameters = new ParameterAnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (ParameterAnnotationRecord.TryReadParameterAnnotation(ref reader, out var parameter) == false)
+ return false;
+
+ parameters[i] = parameter;
+ }
+
+ attribute = new RuntimeInvisibleParameterAnnotationsAttributeRecord(parameters);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeInvisibleTypeAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleTypeAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..81cc47298f
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeInvisibleTypeAnnotationsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeInvisibleTypeAnnotationsAttributeRecord(TypeAnnotationRecord[] Annotations) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeInvisibleTypeAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var annotations = new TypeAnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (TypeAnnotationRecord.TryReadTypeAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ annotations[i] = annotation;
+ }
+
+ attribute = new RuntimeInvisibleTypeAnnotationsAttributeRecord(annotations);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeVisibleAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeVisibleAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..7fb6dd7793
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeVisibleAnnotationsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeVisibleAnnotationsAttributeRecord(AnnotationRecord[] Annotations) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeVisibleAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var items = new AnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (AnnotationRecord.TryReadAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ items[i] = annotation;
+ }
+
+ attribute = new RuntimeVisibleAnnotationsAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeVisibleParameterAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeVisibleParameterAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..5990ba25da
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeVisibleParameterAnnotationsAttributeRecord.cs
@@ -0,0 +1,29 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeVisibleParameterAnnotationsAttributeRecord(ParameterAnnotationRecord[] Parameters) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeVisibleParameterAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU1(out byte count) == false)
+ return false;
+
+ var items = new ParameterAnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (ParameterAnnotationRecord.TryReadParameterAnnotation(ref reader, out var parameter) == false)
+ return false;
+
+ items[i] = parameter;
+ }
+
+ attribute = new RuntimeVisibleParameterAnnotationsAttributeRecord(items);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/RuntimeVisibleTypeAnnotationsAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/RuntimeVisibleTypeAnnotationsAttributeRecord.cs
new file mode 100644
index 0000000000..fde2094726
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/RuntimeVisibleTypeAnnotationsAttributeRecord.cs
@@ -0,0 +1,61 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record RuntimeVisibleTypeAnnotationsAttributeRecord(TypeAnnotationRecord[] Annotations) : AttributeRecord
+ {
+
+ public static bool TryReadRuntimeVisibleTypeAnnotationsAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var annotations = new TypeAnnotationRecord[count];
+ for (int i = 0; i < count; i++)
+ {
+ if (TypeAnnotationRecord.TryReadTypeAnnotation(ref reader, out var annotation) == false)
+ return false;
+
+ annotations[i] = annotation;
+ }
+
+ attribute = new RuntimeVisibleTypeAnnotationsAttributeRecord(annotations);
+ return true;
+ }
+
+ ///
+ /// Gets the number of bytes required to write the record.
+ ///
+ ///
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(ushort);
+
+ foreach (var element in Annotations)
+ size += element.GetSize();
+
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2((ushort)Annotations.Length) == false)
+ return false;
+
+ foreach (var record in Annotations)
+ if (record.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/SameExtendedStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/SameExtendedStackMapFrameRecord.cs
new file mode 100644
index 0000000000..00e8a0f50b
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SameExtendedStackMapFrameRecord.cs
@@ -0,0 +1,21 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record SameExtendedStackMapFrameRecord(byte Tag, ushort OffsetDelta) : StackMapFrameRecord(Tag)
+ {
+
+ public static bool TryReadSameExtendedStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU2(out ushort offsetDelta) == false)
+ return false;
+
+ frame = new SameExtendedStackMapFrameRecord(tag, offsetDelta);
+ return true;
+ }
+
+ }
+
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SameLocalsOneExtendedStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/SameLocalsOneExtendedStackMapFrameRecord.cs
new file mode 100644
index 0000000000..60009a85b9
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SameLocalsOneExtendedStackMapFrameRecord.cs
@@ -0,0 +1,22 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record SameLocalsOneExtendedStackMapFrameRecord(byte FrameType, ushort OffsetDelta, VerificationTypeInfoRecord Stack) : StackMapFrameRecord(FrameType)
+ {
+
+ public static bool TryReadSameLocalsOneStackItemExtendedStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU2(out ushort offsetDelta) == false)
+ return false;
+ if (VerificationTypeInfoRecord.TryReadVerificationTypeInfo(ref reader, out var verificationTypeInfo) == false)
+ return false;
+
+ frame = new SameLocalsOneExtendedStackMapFrameRecord(tag, offsetDelta, verificationTypeInfo);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SameLocalsOneStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/SameLocalsOneStackMapFrameRecord.cs
new file mode 100644
index 0000000000..7ee6b0a890
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SameLocalsOneStackMapFrameRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record SameLocalsOneStackMapFrameRecord(byte FrameType, VerificationTypeInfoRecord Stack) : StackMapFrameRecord(FrameType)
+ {
+
+ public static bool TryReadSameLocalsOneStackItemStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (VerificationTypeInfoRecord.TryReadVerificationTypeInfo(ref reader, out var verificationTypeInfo) == false)
+ return false;
+
+ frame = new SameLocalsOneStackMapFrameRecord(tag, verificationTypeInfo);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SameStackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/SameStackMapFrameRecord.cs
new file mode 100644
index 0000000000..f5e61a2ec7
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SameStackMapFrameRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record SameStackMapFrameRecord(byte Tag) : StackMapFrameRecord(Tag)
+ {
+
+ public static bool TryReadSameStackMapFrame(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ frame = new SameStackMapFrameRecord(tag);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SignatureAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/SignatureAttributeRecord.cs
new file mode 100644
index 0000000000..5416870050
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SignatureAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record SignatureAttributeRecord(ushort SignatureIndex) : AttributeRecord
+ {
+
+ public static bool TryReadSignatureAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort signatureIndex) == false)
+ return false;
+
+ attribute = new SignatureAttributeRecord(signatureIndex);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/SourceDebugExtensionAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/SourceDebugExtensionAttributeRecord.cs
new file mode 100644
index 0000000000..34ffa8e832
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SourceDebugExtensionAttributeRecord.cs
@@ -0,0 +1,25 @@
+using System.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record SourceDebugExtensionAttributeRecord(byte[] Data) : AttributeRecord
+ {
+
+ public static bool TryReadSourceDebugExtensionAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadManyU1(reader.Length, out ReadOnlySequence data) == false)
+ return false;
+
+ var dataBuffer = new byte[data.Length];
+ data.CopyTo(dataBuffer);
+
+ attribute = new SourceDebugExtensionAttributeRecord(dataBuffer);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SourceFileAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/SourceFileAttributeRecord.cs
new file mode 100644
index 0000000000..22d3357814
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SourceFileAttributeRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record SourceFileAttributeRecord(ushort SourceFileIndex) : AttributeRecord
+ {
+
+ public static bool TryReadSourceFileAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort sourceFileIndex) == false)
+ return false;
+
+ attribute = new SourceFileAttributeRecord(sourceFileIndex);
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/StackMapFrameRecord.cs b/src/IKVM.ByteCode/Parsing/StackMapFrameRecord.cs
new file mode 100644
index 0000000000..dbc5cb6613
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/StackMapFrameRecord.cs
@@ -0,0 +1,42 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record StackMapFrameRecord(byte FrameType)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out StackMapFrameRecord frame)
+ {
+ frame = null;
+
+ if (reader.TryReadU1(out byte tag) == false)
+ return false;
+
+ if (TryRead(ref reader, tag, out frame) == false)
+ return false;
+
+ return true;
+ }
+
+ public static bool TryRead(ref ClassFormatReader reader, byte tag, out StackMapFrameRecord frame)
+ {
+ if (tag >= 0 && tag <= 63)
+ return SameStackMapFrameRecord.TryReadSameStackMapFrame(ref reader, tag, out frame);
+ else if (tag >= 64 && tag <= 127)
+ return SameLocalsOneStackMapFrameRecord.TryReadSameLocalsOneStackItemStackMapFrame(ref reader, tag, out frame);
+ else if (tag == 247)
+ return SameLocalsOneExtendedStackMapFrameRecord.TryReadSameLocalsOneStackItemExtendedStackMapFrame(ref reader, tag, out frame);
+ else if (tag >= 248 && tag <= 250)
+ return ChopStackMapFrameRecord.TryReadChopStackMapFrame(ref reader, tag, out frame);
+ else if (tag == 251)
+ return SameExtendedStackMapFrameRecord.TryReadSameExtendedStackMapFrame(ref reader, tag, out frame);
+ else if (tag >= 252 && tag <= 254)
+ return AppendStackMapFrameRecord.TryReadAppendStackMapFrame(ref reader, tag, out frame);
+ else if (tag == 255)
+ return FullStackMapFrameRecord.TryReadFullStackMapFrame(ref reader, tag, out frame);
+ else
+ throw new ByteCodeException($"Invalid stack map frame tag value: '{tag}'.");
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/StackMapTableAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/StackMapTableAttributeRecord.cs
new file mode 100644
index 0000000000..925e394091
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/StackMapTableAttributeRecord.cs
@@ -0,0 +1,25 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record StackMapTableAttributeRecord(StackMapFrameRecord[] Frames) : AttributeRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = null;
+
+ if (reader.TryReadU2(out ushort count) == false)
+ return false;
+
+ var frames = new StackMapFrameRecord[count];
+ for (int i = 0; i < count; i++)
+ if (StackMapFrameRecord.TryRead(ref reader, out frames[i]) == false)
+ return false;
+
+ attribute = new StackMapTableAttributeRecord(frames);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/StringConstantRecord.cs b/src/IKVM.ByteCode/Parsing/StringConstantRecord.cs
new file mode 100644
index 0000000000..19330f9330
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/StringConstantRecord.cs
@@ -0,0 +1,26 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record StringConstantRecord(ushort ValueIndex) : ConstantRecord
+ {
+
+ ///
+ /// Parses a Class constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadStringConstant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort nameIndex) == false)
+ return false;
+
+ constant = new StringConstantRecord(nameIndex);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/SyntheticAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/SyntheticAttributeRecord.cs
new file mode 100644
index 0000000000..cc1846404d
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/SyntheticAttributeRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record SyntheticAttributeRecord : AttributeRecord
+ {
+
+ public static bool TryReadSyntheticAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ attribute = new SyntheticAttributeRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TopVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/TopVariableInfoRecord.cs
new file mode 100644
index 0000000000..bcbecce5c2
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TopVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TopVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new TopVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypeAndNameConstantRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAndNameConstantRecord.cs
new file mode 100644
index 0000000000..c797079829
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAndNameConstantRecord.cs
@@ -0,0 +1,6 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAndNameConstantRecord(ushort NameIndex, ushort DescriptorIndex) : ConstantRecord;
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationCatchTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationCatchTargetRecord.cs
new file mode 100644
index 0000000000..8b0210b532
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationCatchTargetRecord.cs
@@ -0,0 +1,44 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationCatchTargetRecord(ushort ExceptionTableIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort exceptionTableIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationCatchTargetRecord(exceptionTableIndex);
+ return true;
+ }
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(ExceptionTableIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationEmptyTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationEmptyTargetRecord.cs
new file mode 100644
index 0000000000..99fe7fcff6
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationEmptyTargetRecord.cs
@@ -0,0 +1,30 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationEmptyTargetRecord : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = new TypeAnnotationEmptyTargetRecord();
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ return 0;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationFormalParameterTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationFormalParameterTargetRecord.cs
new file mode 100644
index 0000000000..90c9e095e6
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationFormalParameterTargetRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationFormalParameterTargetRecord(byte ParameterIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU1(out byte parameterIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationFormalParameterTargetRecord(parameterIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(byte);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1(ParameterIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetItemRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetItemRecord.cs
new file mode 100644
index 0000000000..e76d10bafe
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetItemRecord.cs
@@ -0,0 +1,56 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct TypeAnnotationLocalVarTargetItemRecord(ushort Offset, ushort Length, ushort Index)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationLocalVarTargetItemRecord record)
+ {
+ record = default;
+
+ if (reader.TryReadU2(out ushort offset) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+
+ if (reader.TryReadU2(out ushort index) == false)
+ return false;
+
+ record = new TypeAnnotationLocalVarTargetItemRecord(offset, length, index);
+ return true;
+ }
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ length += sizeof(ushort);
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(Offset) == false)
+ return false;
+ if (writer.TryWriteU2(Length) == false)
+ return false;
+ if (writer.TryWriteU2(Index) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetRecord.cs
new file mode 100644
index 0000000000..ef82fc5c03
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationLocalVarTargetRecord.cs
@@ -0,0 +1,57 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationLocalVarTargetRecord(TypeAnnotationLocalVarTargetItemRecord[] Items) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+
+ var items = new TypeAnnotationLocalVarTargetItemRecord[length];
+ for (int i = 0; i < length; i++)
+ if (TypeAnnotationLocalVarTargetItemRecord.TryRead(ref reader, out items[i]) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationLocalVarTargetRecord(items);
+ return true;
+ }
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+
+ foreach (var item in Items)
+ length += item.GetSize();
+
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2((ushort)Items.Length) == false)
+ return false;
+
+ foreach (var item in Items)
+ if (item.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationOffsetTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationOffsetTargetRecord.cs
new file mode 100644
index 0000000000..4babfae72c
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationOffsetTargetRecord.cs
@@ -0,0 +1,44 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationOffsetTargetRecord(ushort Offset) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort offset) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationCatchTargetRecord(offset);
+ return true;
+ }
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(Offset) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterBoundTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterBoundTargetRecord.cs
new file mode 100644
index 0000000000..c11930bfb8
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterBoundTargetRecord.cs
@@ -0,0 +1,45 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationParameterBoundTargetRecord(byte ParameterIndex, byte BoundIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU1(out byte parameterIndex) == false)
+ return false;
+ if (reader.TryReadU1(out byte boundIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationParameterBoundTargetRecord(parameterIndex, boundIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(byte);
+ length += sizeof(byte);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1(ParameterIndex) == false)
+ return false;
+ if (writer.TryWriteU1(BoundIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterTargetRecord.cs
new file mode 100644
index 0000000000..37e31c886d
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationParameterTargetRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationParameterTargetRecord(byte ParameterIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU1(out byte parameterIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationParameterTargetRecord( parameterIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(byte);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1(ParameterIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationRecord.cs
new file mode 100644
index 0000000000..01fa9b68e3
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationRecord.cs
@@ -0,0 +1,104 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct TypeAnnotationRecord(TypeAnnotationTargetType TargetType, TypeAnnotationTargetRecord Target, TypePathRecord TargetPath, ushort TypeIndex, params ElementValuePairRecord[] Elements)
+ {
+
+ public static bool TryReadTypeAnnotation(ref ClassFormatReader reader, out TypeAnnotationRecord annotation)
+ {
+ annotation = default;
+
+ if (reader.TryReadU1(out byte targetType) == false)
+ return false;
+ if (TryReadTarget(ref reader, (TypeAnnotationTargetType)targetType, out var target) == false)
+ return false;
+ if (TypePathRecord.TryRead(ref reader, out var targetPath) == false)
+ return false;
+ if (reader.TryReadU2(out ushort typeIndex) == false)
+ return false;
+ if (reader.TryReadU2(out ushort pairCount) == false)
+ return false;
+
+ var elements = new ElementValuePairRecord[pairCount];
+ for (int i = 0; i < pairCount; i++)
+ if (ElementValuePairRecord.TryRead(ref reader, out elements[i]) == false)
+ return false;
+
+ annotation = new TypeAnnotationRecord((TypeAnnotationTargetType)targetType, target, targetPath, typeIndex, elements);
+ return true;
+ }
+
+ public static bool TryReadTarget(ref ClassFormatReader reader, TypeAnnotationTargetType targetType, out TypeAnnotationTargetRecord targetInfo) => targetType switch
+ {
+ TypeAnnotationTargetType.ClassTypeParameter => TypeAnnotationParameterTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodTypeParameter => TypeAnnotationParameterTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ClassExtends => TypeAnnotationSuperTypeTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ClassTypeParameterBound => TypeAnnotationParameterBoundTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodTypeParameterBound => TypeAnnotationParameterBoundTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.Field => TypeAnnotationEmptyTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodReturn => TypeAnnotationEmptyTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodReceiver => TypeAnnotationEmptyTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodFormalParameter => TypeAnnotationFormalParameterTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.Throws => TypeAnnotationThrowsTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.LocalVariable => TypeAnnotationLocalVarTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ResourceVariable => TypeAnnotationLocalVarTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ExceptionParameter => TypeAnnotationCatchTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.InstanceOf => TypeAnnotationOffsetTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.New => TypeAnnotationOffsetTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ConstructorReference => TypeAnnotationOffsetTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodReference => TypeAnnotationOffsetTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.Cast => TypeAnnotationTypeArgumentTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ConstructorInvocationTypeArgument => TypeAnnotationTypeArgumentTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodInvocationTypeArgument => TypeAnnotationTypeArgumentTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.ConstructorReferenceTypeArgument => TypeAnnotationTypeArgumentTargetRecord.TryRead(ref reader, out targetInfo),
+ TypeAnnotationTargetType.MethodReferenceTypeArgument => TypeAnnotationTypeArgumentTargetRecord.TryRead(ref reader, out targetInfo),
+ _ => throw new ByteCodeException($"Invalid type annotation target type: '0x{targetType:X}'."),
+ };
+
+ ///
+ /// Gets the number of bytes required to write the record.
+ ///
+ ///
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(byte);
+ size += Target.GetSize();
+ size += TargetPath.GetSize();
+ size += sizeof(ushort);
+ size += sizeof(ushort);
+
+ for (int i = 0; i < Elements.Length; i++)
+ size += Elements[i].GetSize();
+
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1((byte)TargetType) == false)
+ return false;
+ if (Target.TryWrite(ref writer) == false)
+ return false;
+ if (TargetPath.TryWrite(ref writer) == false)
+ return false;
+ if (writer.TryWriteU2(TypeIndex) == false)
+ return false;
+ if (writer.TryWriteU2((ushort)Elements.Length) == false)
+ return false;
+
+ foreach (var record in Elements)
+ if (record.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationSuperTypeTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationSuperTypeTargetRecord.cs
new file mode 100644
index 0000000000..bfe06378a0
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationSuperTypeTargetRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationSuperTypeTargetRecord(ushort SuperTypeIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort superTypeIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationSuperTypeTargetRecord(superTypeIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(SuperTypeIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetRecord.cs
new file mode 100644
index 0000000000..71df0c3a77
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetRecord.cs
@@ -0,0 +1,22 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record TypeAnnotationTargetRecord
+ {
+
+ ///
+ /// Returns the size of the record when written.
+ ///
+ ///
+ public abstract int GetSize();
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public abstract bool TryWrite(ref ClassFormatWriter writer);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetType.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetType.cs
new file mode 100644
index 0000000000..4c9fbba312
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationTargetType.cs
@@ -0,0 +1,32 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal enum TypeAnnotationTargetType : byte
+ {
+
+ ClassTypeParameter = 0x00,
+ MethodTypeParameter = 0x01,
+ ClassExtends = 0x10,
+ ClassTypeParameterBound = 0x11,
+ MethodTypeParameterBound = 0x12,
+ Field = 0x13,
+ MethodReturn = 0x14,
+ MethodReceiver = 0x15,
+ MethodFormalParameter = 0x16,
+ Throws = 0x17,
+ LocalVariable = 0x40,
+ ResourceVariable = 0x41,
+ ExceptionParameter = 0x42,
+ InstanceOf = 0x43,
+ New = 0x44,
+ ConstructorReference = 0x45,
+ MethodReference = 0x46,
+ Cast = 0x47,
+ ConstructorInvocationTypeArgument = 0x48,
+ MethodInvocationTypeArgument = 0x49,
+ ConstructorReferenceTypeArgument = 0x4A,
+ MethodReferenceTypeArgument = 0x4B,
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationThrowsTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationThrowsTargetRecord.cs
new file mode 100644
index 0000000000..cd5aa44d8f
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationThrowsTargetRecord.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationThrowsTargetRecord(ushort ThrowsTypeIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort throwsTypeIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationThrowsTargetRecord(throwsTypeIndex);
+ return true;
+ }
+
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(ThrowsTypeIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypeAnnotationTypeArgumentTargetRecord.cs b/src/IKVM.ByteCode/Parsing/TypeAnnotationTypeArgumentTargetRecord.cs
new file mode 100644
index 0000000000..69eb461987
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypeAnnotationTypeArgumentTargetRecord.cs
@@ -0,0 +1,49 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record TypeAnnotationTypeArgumentTargetRecord(ushort Offset, ushort TypeArgumentIndex) : TypeAnnotationTargetRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypeAnnotationTargetRecord targetInfo)
+ {
+ targetInfo = null;
+
+ if (reader.TryReadU2(out ushort offset) == false)
+ return false;
+ if (reader.TryReadU2(out ushort typeArgumentIndex) == false)
+ return false;
+
+ targetInfo = new TypeAnnotationTypeArgumentTargetRecord(offset, typeArgumentIndex);
+ return true;
+ }
+
+ ///
+ /// Gets the size of the record if written.
+ ///
+ ///
+ public override int GetSize()
+ {
+ var length = 0;
+ length += sizeof(ushort);
+ length += sizeof(ushort);
+ return length;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public override bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU2(Offset) == false)
+ return false;
+ if (writer.TryWriteU2(TypeArgumentIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Parsing/TypePathItemRecord.cs b/src/IKVM.ByteCode/Parsing/TypePathItemRecord.cs
new file mode 100644
index 0000000000..f67f1baebe
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypePathItemRecord.cs
@@ -0,0 +1,45 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct TypePathItemRecord(TypePathKind Kind, byte ArgumentIndex)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypePathItemRecord record)
+ {
+ record = default;
+
+ if (reader.TryReadU1(out byte kind) == false)
+ return false;
+ if (reader.TryReadU1(out byte argumentIndex) == false)
+ return false;
+
+ record = new TypePathItemRecord((TypePathKind)kind, argumentIndex);
+ return true;
+ }
+
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(byte);
+ size += sizeof(byte);
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1((byte)Kind) == false)
+ return false;
+ if (writer.TryWriteU1(ArgumentIndex) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/TypePathRecord.cs b/src/IKVM.ByteCode/Parsing/TypePathRecord.cs
new file mode 100644
index 0000000000..b351bc464a
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/TypePathRecord.cs
@@ -0,0 +1,57 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record struct TypePathRecord(params TypePathItemRecord[] Path)
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out TypePathRecord typePath)
+ {
+ typePath = default;
+
+ if (reader.TryReadU1(out byte length) == false)
+ return false;
+
+ var path = new TypePathItemRecord[length];
+ for (int i = 0; i < length; i++)
+ {
+ if (TypePathItemRecord.TryRead(ref reader, out var item) == false)
+ return false;
+
+ path[i] = item;
+ }
+
+ typePath = new TypePathRecord(path);
+ return true;
+ }
+
+ public int GetSize()
+ {
+ var size = 0;
+ size += sizeof(byte);
+
+ foreach (var item in Path)
+ size += item.GetSize();
+
+ return size;
+ }
+
+ ///
+ /// Attempts to write the record to the given .
+ ///
+ ///
+ ///
+ public bool TryWrite(ref ClassFormatWriter writer)
+ {
+ if (writer.TryWriteU1((byte)Path.Length) == false)
+ return false;
+
+ foreach (var item in Path)
+ if (item.TryWrite(ref writer) == false)
+ return false;
+
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/UninitializedThisVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/UninitializedThisVariableInfoRecord.cs
new file mode 100644
index 0000000000..6b52843f36
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/UninitializedThisVariableInfoRecord.cs
@@ -0,0 +1,15 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record UninitializedThisVariableInfoRecord : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = new UninitializedThisVariableInfoRecord();
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/UninitializedVariableInfoRecord.cs b/src/IKVM.ByteCode/Parsing/UninitializedVariableInfoRecord.cs
new file mode 100644
index 0000000000..8a16e8df7c
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/UninitializedVariableInfoRecord.cs
@@ -0,0 +1,20 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record UninitializedVariableInfoRecord(ushort Offset) : VerificationTypeInfoRecord
+ {
+
+ public static bool TryRead(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = null;
+
+ if (reader.TryReadU2(out ushort offset) == false)
+ return false;
+
+ record = new UninitializedVariableInfoRecord(offset);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/UnknownAttributeRecord.cs b/src/IKVM.ByteCode/Parsing/UnknownAttributeRecord.cs
new file mode 100644
index 0000000000..50d2949772
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/UnknownAttributeRecord.cs
@@ -0,0 +1,18 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal sealed record UnknownAttributeRecord(byte[] Data) : AttributeRecord
+ {
+
+ public static bool TryReadCustomAttribute(ref ClassFormatReader reader, out AttributeRecord attribute)
+ {
+ var data = new byte[reader.Length];
+ reader.TryCopyTo(data);
+
+ attribute = new UnknownAttributeRecord(data);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/Utf8ConstantRecord.cs b/src/IKVM.ByteCode/Parsing/Utf8ConstantRecord.cs
new file mode 100644
index 0000000000..548db6b53f
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/Utf8ConstantRecord.cs
@@ -0,0 +1,33 @@
+using System.Buffers;
+
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal record class Utf8ConstantRecord(byte[] Value) : ConstantRecord
+ {
+
+ ///
+ /// Parses a UTF8 constant in the constant pool.
+ ///
+ ///
+ ///
+ public static bool TryReadUtf8Constant(ref ClassFormatReader reader, out ConstantRecord constant, out int skip)
+ {
+ constant = null;
+ skip = 0;
+
+ if (reader.TryReadU2(out ushort length) == false)
+ return false;
+ if (reader.TryReadManyU1(length, out ReadOnlySequence value) == false)
+ return false;
+
+ var valueBuffer = new byte[value.Length];
+ value.CopyTo(valueBuffer);
+
+ constant = new Utf8ConstantRecord(valueBuffer);
+ return true;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Parsing/VerificationTypeInfoRecord.cs b/src/IKVM.ByteCode/Parsing/VerificationTypeInfoRecord.cs
new file mode 100644
index 0000000000..51a5e5c7c0
--- /dev/null
+++ b/src/IKVM.ByteCode/Parsing/VerificationTypeInfoRecord.cs
@@ -0,0 +1,31 @@
+namespace IKVM.ByteCode.Parsing
+{
+
+ internal abstract record VerificationTypeInfoRecord
+ {
+
+ public static bool TryReadVerificationTypeInfo(ref ClassFormatReader reader, out VerificationTypeInfoRecord record)
+ {
+ record = null;
+
+ if (reader.TryReadU1(out byte tag) == false)
+ return false;
+
+ return tag switch
+ {
+ 0 => TopVariableInfoRecord.TryRead(ref reader, out record),
+ 1 => IntegerVariableInfoRecord.TryRead(ref reader, out record),
+ 2 => FloatVariableInfoRecord.TryRead(ref reader, out record),
+ 3 => DoubleVariableInfoRecord.TryRead(ref reader, out record),
+ 4 => LongVariableInfoRecord.TryRead(ref reader, out record),
+ 5 => NullVariableInfoRecord.TryRead(ref reader, out record),
+ 6 => UninitializedThisVariableInfoRecord.TryRead(ref reader, out record),
+ 7 => ObjectVariableInfoRecord.TryRead(ref reader, out record),
+ 8 => UninitializedVariableInfoRecord.TryRead(ref reader, out record),
+ _ => throw new ByteCodeException($"Invalid verification info tag: '{tag}'."),
+ };
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/README.md b/src/IKVM.ByteCode/README.md
new file mode 100644
index 0000000000..22312bf091
--- /dev/null
+++ b/src/IKVM.ByteCode/README.md
@@ -0,0 +1,20 @@
+# IKVM.ByteCode
+
+Provides a Java class file parser, reader and writer implementation used by the IKVM project.
+
+The project is organized into three related sections.
+
+## IKVM.ByteCode.Parsing
+
+Contains Record structures and classes that represent the raw values that might appear in a Java class file. These
+types are not blittable, but do contain a TryRead method and TryWrite method, targeting a ClassFormatReader and
+ClassFormatWriter respectively.
+
+## IVKM.ByteCode.Reading
+
+Contains Reader classes that wrap Record structures. The main top-level class is ClassReader. ClassReader operates on
+a ClassRecord. ClassReader exposes collections and properties which allow navigation through a parsed class file. An
+attempt is made to only initialize data from the underlying records on demand.
+
+The Readers allow you to navigate through the constant pool, resolve constant values, and navigate through the
+interfaces, fields, methods and attributes of the class.
diff --git a/src/IKVM.ByteCode/Reading/AnnotationDefaultAttributeReader.cs b/src/IKVM.ByteCode/Reading/AnnotationDefaultAttributeReader.cs
new file mode 100644
index 0000000000..fe036cbef3
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AnnotationDefaultAttributeReader.cs
@@ -0,0 +1,29 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class AnnotationDefaultAttributeReader : AttributeReader
+ {
+
+ ElementValueReader defaultValue;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal AnnotationDefaultAttributeReader(ClassReader declaringClass, AttributeInfoReader info, AnnotationDefaultAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ public ElementValueReader DefaultValue => LazyGet(ref defaultValue, () => ElementValueReader.Resolve(DeclaringClass, Record.DefaultValue));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/AnnotationReader.cs b/src/IKVM.ByteCode/Reading/AnnotationReader.cs
new file mode 100644
index 0000000000..5a9b50c55a
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AnnotationReader.cs
@@ -0,0 +1,37 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class AnnotationReader : ReaderBase
+ {
+
+ Utf8ConstantReader type;
+ ElementValueKeyReaderCollection elements;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public AnnotationReader(ClassReader declaringClass, AnnotationRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the type of the annotation.
+ ///
+ public Utf8ConstantReader Type => LazyGet(ref type, () => DeclaringClass.Constants.Get(Record.TypeIndex));
+
+ ///
+ /// Gets the element values of the annotation.
+ ///
+ public ElementValueKeyReaderCollection Elements => LazyGet(ref elements, () => new ElementValueKeyReaderCollection(DeclaringClass, Record.Elements));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/AnnotationReaderCollection.cs b/src/IKVM.ByteCode/Reading/AnnotationReaderCollection.cs
new file mode 100644
index 0000000000..b132856a6e
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AnnotationReaderCollection.cs
@@ -0,0 +1,36 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of annotation data.
+ ///
+ internal sealed class AnnotationReaderCollection : LazyReaderList
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public AnnotationReaderCollection(ClassReader declaringClass, AnnotationRecord[] records) :
+ base(declaringClass, records)
+ {
+
+ }
+
+ ///
+ /// Creates a new reader.
+ ///
+ ///
+ ///
+ ///
+ protected override AnnotationReader CreateReader(int index, AnnotationRecord record)
+ {
+ return new AnnotationReader(DeclaringClass, record);
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/AttributeInfoReader.cs b/src/IKVM.ByteCode/Reading/AttributeInfoReader.cs
new file mode 100644
index 0000000000..dc6403d415
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AttributeInfoReader.cs
@@ -0,0 +1,36 @@
+using System;
+
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class AttributeInfoReader : ReaderBase
+ {
+
+ Utf8ConstantReader name;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal AttributeInfoReader(ClassReader declaringClass, AttributeInfoRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the name of the attribute.
+ ///
+ public Utf8ConstantReader Name => name ??= DeclaringClass.Constants.Get(Record.NameIndex);
+
+ ///
+ /// Gets the data of the attribute.
+ ///
+ public ReadOnlyMemory Data => Record.Data;
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/AttributeReader.cs b/src/IKVM.ByteCode/Reading/AttributeReader.cs
new file mode 100644
index 0000000000..a87b5ecaaf
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AttributeReader.cs
@@ -0,0 +1,64 @@
+using System;
+
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal abstract class AttributeReader : ReaderBase
+ {
+
+ readonly AttributeInfoReader info;
+ readonly AttributeRecord record;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal AttributeReader(ClassReader declaringClass, AttributeInfoReader info, AttributeRecord record) :
+ base(declaringClass)
+ {
+ this.info = info ?? throw new ArgumentNullException(nameof(info));
+ this.record = record ?? throw new ArgumentNullException(nameof(record));
+ }
+
+ ///
+ /// Gets the information about the attribute.
+ ///
+ public AttributeInfoReader Info => info;
+
+ ///
+ /// Gets the underlying record of the attribute.
+ ///
+ public AttributeRecord Record => record;
+
+ }
+
+ internal abstract class AttributeReader : AttributeReader
+ where TRecord : AttributeRecord
+ {
+
+ readonly TRecord record;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal AttributeReader(ClassReader declaringClass, AttributeInfoReader info, TRecord record) :
+ base(declaringClass, info, record)
+ {
+ this.record = record ?? throw new ArgumentNullException(nameof(record));
+ }
+
+ ///
+ /// Gets the underlying data record of the attribute.
+ ///
+ public new TRecord Record => record;
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/AttributeReaderCollection.cs b/src/IKVM.ByteCode/Reading/AttributeReaderCollection.cs
new file mode 100644
index 0000000000..f5356e78f9
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/AttributeReaderCollection.cs
@@ -0,0 +1,196 @@
+using System.Collections.Generic;
+using System.Linq;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of attribute data.
+ ///
+ internal sealed class AttributeReaderCollection : LazyReaderList
+ {
+
+ ConstantValueAttributeReader constantValueAttribute;
+ CodeAttributeReader codeAttribute;
+ StackMapTableAttributeReader stackMapTableAttribute;
+ ExceptionsAttributeReader exceptionsAttribute;
+ InnerClassesAttributeReader innerClassesAttribute;
+ EnclosingMethodAttributeReader enclosingMethodAttribute;
+ SyntheticAttributeReader syntheticAttribute;
+ SignatureAttributeReader signatureAttribute;
+ SourceFileAttributeReader sourceFileAttribute;
+ SourceDebugExtensionAttributeReader sourceDebugExtensionAttribute;
+ LineNumberTableAttributeReader lineNumberTableAttribute;
+ LocalVariableTableAttributeReader localVariableTableAttribute;
+ LocalVariableTypeTableAttributeReader localVariableTypeTableAttribute;
+ DeprecatedAttributeReader deprecatedAttribute;
+ RuntimeVisibleAnnotationsAttributeReader runtimeVisibleAnnotationsAttribute;
+ RuntimeInvisibleAnnotationsAttributeReader runtimeInvisibleAnnotationsAttribute;
+ RuntimeVisibleParameterAnnotationsAttributeReader runtimeVisibleParameterAnnotationsAttribute;
+ RuntimeInvisibleParameterAnnotationsAttributeReader runtimeInvisibleParameterAnnotationsAttribute;
+ RuntimeVisibleTypeAnnotationsAttributeReader runtimeVisibleTypeAnnotationsAttribute;
+ RuntimeInvisibleTypeAnnotationsAttributeReader runtimeInvisibleTypeAnnotationsAttribute;
+ AnnotationDefaultAttributeReader annotationDefaultAttribute;
+ BootstrapMethodsAttributeReader bootstrapMethodsAttribute;
+ MethodParametersAttributeReader methodParametersAttribute;
+ ModuleAttributeReader moduleAttribute;
+ ModulePackagesAttributeReader modulePackagesAttribute;
+ ModuleMainClassAttributeReader moduleMainClassAttribute;
+ NestHostAttributeReader nestHostAttribute;
+ NestMembersAttributeReader nestMembersAttribute;
+ RecordAttributeReader recordAttribute;
+ PermittedSubclassesAttributeReader permittedSubclassesAttribute;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public AttributeReaderCollection(ClassReader declaringClass, AttributeInfoRecord[] records) :
+ base(declaringClass, records)
+ {
+
+ }
+
+ ///
+ /// Creates a new reader.
+ ///
+ ///
+ ///
+ ///
+ ///
+ protected override AttributeReader CreateReader(int index, AttributeInfoRecord record)
+ {
+ var info = new AttributeInfoReader(DeclaringClass, record);
+ if (AttributeRecord.TryRead(info, out var attribute) == false)
+ throw new ByteCodeException("Unable to read attribute data.");
+
+ return attribute switch
+ {
+ ConstantValueAttributeRecord d => new ConstantValueAttributeReader(DeclaringClass, info, d),
+ CodeAttributeRecord d => new CodeAttributeReader(DeclaringClass, info, d),
+ StackMapTableAttributeRecord d => new StackMapTableAttributeReader(DeclaringClass, info, d),
+ ExceptionsAttributeRecord d => new ExceptionsAttributeReader(DeclaringClass, info, d),
+ InnerClassesAttributeRecord d => new InnerClassesAttributeReader(DeclaringClass, info, d),
+ EnclosingMethodAttributeRecord d => new EnclosingMethodAttributeReader(DeclaringClass, info, d),
+ SyntheticAttributeRecord d => new SyntheticAttributeReader(DeclaringClass, info, d),
+ SignatureAttributeRecord d => new SignatureAttributeReader(DeclaringClass, info, d),
+ SourceFileAttributeRecord d => new SourceFileAttributeReader(DeclaringClass, info, d),
+ SourceDebugExtensionAttributeRecord d => new SourceDebugExtensionAttributeReader(DeclaringClass, info, d),
+ LineNumberTableAttributeRecord d => new LineNumberTableAttributeReader(DeclaringClass, info, d),
+ LocalVariableTableAttributeRecord d => new LocalVariableTableAttributeReader(DeclaringClass, info, d),
+ LocalVariableTypeTableAttributeRecord d => new LocalVariableTypeTableAttributeReader(DeclaringClass, info, d),
+ DeprecatedAttributeRecord d => new DeprecatedAttributeReader(DeclaringClass, info, d),
+ RuntimeVisibleAnnotationsAttributeRecord d => new RuntimeVisibleAnnotationsAttributeReader(DeclaringClass, info, d),
+ RuntimeInvisibleAnnotationsAttributeRecord d => new RuntimeInvisibleAnnotationsAttributeReader(DeclaringClass, info, d),
+ RuntimeVisibleParameterAnnotationsAttributeRecord d => new RuntimeVisibleParameterAnnotationsAttributeReader(DeclaringClass, info, d),
+ RuntimeInvisibleParameterAnnotationsAttributeRecord d => new RuntimeInvisibleParameterAnnotationsAttributeReader(DeclaringClass, info, d),
+ RuntimeVisibleTypeAnnotationsAttributeRecord d => new RuntimeVisibleTypeAnnotationsAttributeReader(DeclaringClass, info, d),
+ RuntimeInvisibleTypeAnnotationsAttributeRecord d => new RuntimeInvisibleTypeAnnotationsAttributeReader(DeclaringClass, info, d),
+ AnnotationDefaultAttributeRecord d => new AnnotationDefaultAttributeReader(DeclaringClass, info, d),
+ BootstrapMethodsAttributeRecord d => new BootstrapMethodsAttributeReader(DeclaringClass, info, d),
+ MethodParametersAttributeRecord d => new MethodParametersAttributeReader(DeclaringClass, info, d),
+ ModuleAttributeRecord d => new ModuleAttributeReader(DeclaringClass, info, d),
+ ModulePackagesAttributeRecord d => new ModulePackagesAttributeReader(DeclaringClass, info, d),
+ ModuleMainClassAttributeRecord d => new ModuleMainClassAttributeReader(DeclaringClass, info, d),
+ NestHostAttributeRecord d => new NestHostAttributeReader(DeclaringClass, info, d),
+ NestMembersAttributeRecord d => new NestMembersAttributeReader(DeclaringClass, info, d),
+ RecordAttributeRecord d => new RecordAttributeReader(DeclaringClass, info, d),
+ PermittedSubclassesAttributeRecord d => new PermittedSubclassesAttributeReader(DeclaringClass, info, d),
+ UnknownAttributeRecord d => new UnknownAttributeReader(DeclaringClass, info, d),
+ _ => throw new ByteCodeException("Cannot resolve attribute data."),
+ };
+ }
+
+ ///
+ /// Gets the attribute with the specified name.
+ ///
+ ///
+ ///
+ public AttributeReader this[string name] => this.FirstOrDefault(i => i.Info.Name.Value == name) ?? throw new KeyNotFoundException();
+
+ ///
+ /// Returns true if an attribute with the specified name exists.
+ ///
+ ///
+ ///
+ public bool Contains(string name) => this.Any(i => i.Info.Name.Value == name);
+
+ ///
+ /// Attempts to get the attribute with the specified name.
+ ///
+ ///
+ ///
+ public bool TryGet(out TAttribute value)
+ {
+ value = this.OfType().FirstOrDefault();
+ return value != null;
+ }
+
+ public ConstantValueAttributeReader ConstantValue => LazyGet(ref constantValueAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public CodeAttributeReader Code => LazyGet(ref codeAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public StackMapTableAttributeReader StackMapTable => LazyGet(ref stackMapTableAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public ExceptionsAttributeReader Exceptions => LazyGet(ref exceptionsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public InnerClassesAttributeReader InnerClasses => LazyGet(ref innerClassesAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public EnclosingMethodAttributeReader EnclosingMethod => LazyGet(ref enclosingMethodAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public SyntheticAttributeReader Synthetic => LazyGet(ref syntheticAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public SignatureAttributeReader Signature => LazyGet(ref signatureAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public SourceFileAttributeReader SourceFile => LazyGet(ref sourceFileAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public SourceDebugExtensionAttributeReader SourceDebugExtension => LazyGet(ref sourceDebugExtensionAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public LineNumberTableAttributeReader LineNumberTable => LazyGet(ref lineNumberTableAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public LocalVariableTableAttributeReader LocalVariableTable => LazyGet(ref localVariableTableAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public LocalVariableTypeTableAttributeReader LocalVariableTypeTable => LazyGet(ref localVariableTypeTableAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public DeprecatedAttributeReader Deprecated => LazyGet(ref deprecatedAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeVisibleAnnotationsAttributeReader RuntimeVisibleAnnotations => LazyGet(ref runtimeVisibleAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeInvisibleAnnotationsAttributeReader RuntimeInvisibleAnnotations => LazyGet(ref runtimeInvisibleAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeVisibleParameterAnnotationsAttributeReader RuntimeVisibleParameterAnnotations => LazyGet(ref runtimeVisibleParameterAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeInvisibleParameterAnnotationsAttributeReader RuntimeInvisibleParameterAnnotations => LazyGet(ref runtimeInvisibleParameterAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeVisibleTypeAnnotationsAttributeReader RuntimeVisibleTypeAnnotations => LazyGet(ref runtimeVisibleTypeAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RuntimeInvisibleTypeAnnotationsAttributeReader RuntimeInvisibleTypeAnnotations => LazyGet(ref runtimeInvisibleTypeAnnotationsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public AnnotationDefaultAttributeReader AnnotationDefault => LazyGet(ref annotationDefaultAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public BootstrapMethodsAttributeReader BootstrapMethods => LazyGet(ref bootstrapMethodsAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public MethodParametersAttributeReader MethodParameters => LazyGet(ref methodParametersAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public ModuleAttributeReader Module => LazyGet(ref moduleAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public ModulePackagesAttributeReader ModulePackages => LazyGet(ref modulePackagesAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public ModuleMainClassAttributeReader ModuleMainClass => LazyGet(ref moduleMainClassAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public NestHostAttributeReader NestHost => LazyGet(ref nestHostAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public NestMembersAttributeReader NestMembers => LazyGet(ref nestMembersAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public RecordAttributeReader Record => LazyGet(ref recordAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ public PermittedSubclassesAttributeReader PermittedSubclasses => LazyGet(ref permittedSubclassesAttribute, () => TryGet(out var attribute) ? attribute : null);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeMethodReader.cs b/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeMethodReader.cs
new file mode 100644
index 0000000000..9d8be36f51
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeMethodReader.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class BootstrapMethodsAttributeMethodReader : ReaderBase
+ {
+
+ MethodrefConstantReader methodref;
+ IReadOnlyList arguments;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public BootstrapMethodsAttributeMethodReader(ClassReader declaringClass, BootstrapMethodsAttributeMethodRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the method being referenced.
+ ///
+ public MethodrefConstantReader Methodref => LazyGet(ref methodref, () => DeclaringClass.Constants.Get(Record.MethodRefIndex));
+
+ ///
+ /// Gets the arguments bound to the method reference.
+ ///
+ public IReadOnlyList Arguments => LazyGet(ref arguments, () => new DelegateLazyReaderList(DeclaringClass, Record.Arguments, (_, index) => DeclaringClass.Constants[index]));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeReader.cs b/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeReader.cs
new file mode 100644
index 0000000000..ee487fb355
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/BootstrapMethodsAttributeReader.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal class BootstrapMethodsAttributeReader : AttributeReader
+ {
+
+ IReadOnlyList methods;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal BootstrapMethodsAttributeReader(ClassReader declaringClass, AttributeInfoReader info, BootstrapMethodsAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ ///
+ /// Gets the set of methods described by this bootstrap attribute.
+ ///
+ public IReadOnlyList Methods => LazyGet(ref methods, () => new DelegateLazyReaderList(DeclaringClass, Record.Methods, (_, record) => new BootstrapMethodsAttributeMethodReader(DeclaringClass, record)));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ClassConstantReader.cs b/src/IKVM.ByteCode/Reading/ClassConstantReader.cs
new file mode 100644
index 0000000000..de2ae10291
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ClassConstantReader.cs
@@ -0,0 +1,37 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ClassConstantReader : ConstantReader
+ {
+
+ Utf8ConstantReader name;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public ClassConstantReader(ClassReader declaringClass, ushort index, ClassConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the name of the class.
+ ///
+ public Utf8ConstantReader Name => LazyGet(ref name, () => DeclaringClass.Constants.Get(Record.NameIndex));
+
+ ///
+ /// Returns whether or not this constant is loadable.
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(49, 0);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/ClassReader.cs b/src/IKVM.ByteCode/Reading/ClassReader.cs
new file mode 100644
index 0000000000..aaa90c09d9
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ClassReader.cs
@@ -0,0 +1,252 @@
+using System;
+using System.Buffers;
+using System.IO;
+using System.IO.Pipelines;
+using System.Threading;
+using System.Threading.Tasks;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Provides stateful operations for reading a class file.
+ ///
+ internal sealed class ClassReader : ReaderBase
+ {
+
+ ///
+ /// Attempts to read a class from the given buffer.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(ReadOnlyMemory buffer, out ClassReader clazz)
+ {
+ return TryRead(new ReadOnlySequence(buffer), out clazz);
+ }
+
+ ///
+ /// Attempts to read a class from the given buffer.
+ ///
+ ///
+ ///
+ ///
+ public static ClassReader Read(ReadOnlyMemory buffer)
+ {
+ return TryRead(buffer, out var clazz) ? clazz : throw new ByteCodeException("Failed to open ClassReader. Incomplete class data.");
+ }
+
+ ///
+ /// Attempts to read a class from the given buffer.
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(in ReadOnlySequence buffer, out ClassReader clazz)
+ {
+ var reader = new ClassFormatReader(buffer);
+ return TryRead(ref reader, out clazz);
+ }
+
+ ///
+ /// Attempts to read a class from the given buffer.
+ ///
+ ///
+ ///
+ ///
+ public static ClassReader Read(in ReadOnlySequence buffer)
+ {
+ var reader = new ClassFormatReader(buffer);
+ return TryRead(ref reader, out var clazz) ? clazz : throw new ByteCodeException("Failed to open ClassReader. Incomplete class data.");
+ }
+
+ ///
+ /// Attempts to read a class from the given reader.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static bool TryRead(ref ClassFormatReader reader, out ClassReader clazz)
+ {
+ clazz = null;
+
+ if (ClassRecord.TryRead(ref reader, out var record) == false)
+ return false;
+
+ clazz = new ClassReader(record);
+ return true;
+ }
+
+ ///
+ /// Reads the next class from the stream.
+ ///
+ ///
+ public static async Task ReadAsync(Stream stream, CancellationToken cancellationToken = default)
+ {
+ if (stream is null)
+ throw new ArgumentNullException(nameof(stream));
+
+ var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(minimumReadSize: 1, leaveOpen: true));
+
+ try
+ {
+ return await ReadAsync(reader, cancellationToken);
+ }
+ catch (Exception e)
+ {
+ await reader.CompleteAsync(e);
+ throw;
+ }
+ finally
+ {
+ await reader.CompleteAsync();
+ }
+ }
+
+ ///
+ /// Reads the next class from the stream.
+ ///
+ ///
+ public static async Task ReadAsync(PipeReader reader, CancellationToken cancellationToken = default)
+ {
+ while (true)
+ {
+ var result = await reader.ReadAtLeastAsync(30, cancellationToken);
+ if (result.IsCanceled)
+ throw new OperationCanceledException();
+
+ // attempt to read at least one class
+ if (TryRead(result.Buffer, out var clazz) == false)
+ {
+ reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
+ continue;
+ }
+
+ reader.AdvanceTo(result.Buffer.End);
+ return clazz;
+ }
+ }
+
+ ///
+ /// Reads the next class from the stream.
+ ///
+ ///
+ public static ClassReader Read(Stream stream)
+ {
+ if (stream is null)
+ throw new ArgumentNullException(nameof(stream));
+
+ var reader = PipeReader.Create(stream, new StreamPipeReaderOptions(minimumReadSize: 1, leaveOpen: true));
+
+ try
+ {
+ return Read(reader);
+ }
+ catch (Exception e)
+ {
+ reader.Complete(e);
+ throw;
+ }
+ finally
+ {
+ reader.Complete();
+ }
+ }
+
+ ///
+ /// Reads the next class from the stream.
+ ///
+ ///
+ public static ClassReader Read(PipeReader reader)
+ {
+ while (true)
+ {
+ var result = reader.ReadAtLeastAsync(30, CancellationToken.None).GetAwaiter().GetResult();
+ if (result.IsCanceled)
+ throw new OperationCanceledException();
+
+ // attempt to read at least one class
+ if (TryRead(result.Buffer, out var clazz) == false)
+ {
+ reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);
+ continue;
+ }
+
+ reader.AdvanceTo(result.Buffer.End);
+ return clazz;
+ }
+ }
+
+ internal int MAX_STACK_ALLOC = 1024;
+
+ ClassConstantReader @this;
+ ClassConstantReader super;
+ ConstantReaderCollection constants;
+ InterfaceReaderCollection interfaces;
+ FieldReaderCollection fields;
+ MethodReaderCollection methods;
+ AttributeReaderCollection attributes;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ internal ClassReader(ClassRecord record) :
+ base(null, record)
+ {
+
+ }
+
+ ///
+ /// Gets the version of the class.
+ ///
+ public ClassFormatVersion Version => new ClassFormatVersion(Record.MajorVersion, Record.MinorVersion);
+
+ ///
+ /// Gets the set of constants declared by the class.
+ ///
+ public ConstantReaderCollection Constants => LazyGet(ref constants, () => new ConstantReaderCollection(this, Record.Constants));
+
+ ///
+ /// Gets the access flags of the class.
+ ///
+ public AccessFlag AccessFlags => Record.AccessFlags;
+
+ ///
+ /// Gets the name of the class.
+ ///
+ public ClassConstantReader This => LazyGet(ref @this, () => Constants.Get(Record.ThisClassIndex));
+
+ ///
+ /// Gets the name of the super class.
+ ///
+ public ClassConstantReader Super => LazyGet(ref super, () => Constants.Get(Record.SuperClassIndex));
+
+ ///
+ /// Gets the set of the interfaces implemented by this class.
+ ///
+ public InterfaceReaderCollection Interfaces => LazyGet(ref interfaces, () => new InterfaceReaderCollection(this, Record.Interfaces));
+
+ ///
+ /// Gets the set of fields declared by the class.
+ ///
+ public FieldReaderCollection Fields => LazyGet(ref fields, () => new FieldReaderCollection(this, Record.Fields));
+
+ ///
+ /// Gets the set of methods declared by the class.
+ ///
+ public MethodReaderCollection Methods => LazyGet(ref methods, () => new MethodReaderCollection(this, Record.Methods));
+
+ ///
+ /// Gets the dictionary of attributes declared by the class.
+ ///
+ public AttributeReaderCollection Attributes => LazyGet(ref attributes, () => new AttributeReaderCollection(this, Record.Attributes));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/CodeAttributeReader.cs b/src/IKVM.ByteCode/Reading/CodeAttributeReader.cs
new file mode 100644
index 0000000000..e311b7ff24
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/CodeAttributeReader.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class CodeAttributeReader : AttributeReader
+ {
+
+ AttributeReaderCollection attributes;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal CodeAttributeReader(ClassReader declaringClass, AttributeInfoReader info, CodeAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ public ushort MaxStack => Record.MaxStack;
+
+ public ushort MaxLocals => Record.MaxLocals;
+
+ ///
+ /// Gets the byte code.
+ ///
+ public ReadOnlyMemory Code => Record.Code;
+
+ public IReadOnlyList ExceptionTable => Record.ExceptionTable;
+
+ ///
+ /// Gets the set of attributes applied to this attribute.
+ ///
+ public AttributeReaderCollection Attributes => LazyGet(ref attributes, () => new AttributeReaderCollection(DeclaringClass, Record.Attributes));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ConstantReader.cs b/src/IKVM.ByteCode/Reading/ConstantReader.cs
new file mode 100644
index 0000000000..b3c17646a6
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ConstantReader.cs
@@ -0,0 +1,83 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Provides static methods for reading constants.
+ ///
+ internal static class ConstantReader
+ {
+
+ ///
+ /// Initializes a from a .
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static IConstantReader Read(ClassReader declaringClass, ushort index, ConstantRecord record) => record switch
+ {
+ Utf8ConstantRecord c => new Utf8ConstantReader(declaringClass, index, c),
+ IntegerConstantRecord c => new IntegerConstantReader(declaringClass, index, c),
+ FloatConstantRecord c => new FloatConstantReader(declaringClass, index, c),
+ LongConstantRecord c => new LongConstantReader(declaringClass, index, c),
+ DoubleConstantRecord c => new DoubleConstantReader(declaringClass, index, c),
+ ClassConstantRecord c => new ClassConstantReader(declaringClass, index, c),
+ StringConstantRecord c => new StringConstantReader(declaringClass, index, c),
+ FieldrefConstantRecord c => new FieldrefConstantReader(declaringClass, index, c),
+ MethodrefConstantRecord c => new MethodrefConstantReader(declaringClass, index, c),
+ InterfaceMethodrefConstantRecord c => new InterfaceMethodrefConstantReader(declaringClass, index, c),
+ NameAndTypeConstantRecord c => new NameAndTypeConstantReader(declaringClass, index, c),
+ MethodHandleConstantRecord c => new MethodHandleConstantReader(declaringClass, index, c),
+ MethodTypeConstantRecord c => new MethodTypeConstantReader(declaringClass, index, c),
+ DynamicConstantRecord c => new DynamicConstantReader(declaringClass, index, c),
+ InvokeDynamicConstantRecord c => new InvokeDynamicConstantReader(declaringClass, index, c),
+ ModuleConstantRecord c => new ModuleConstantReader(declaringClass, index, c),
+ PackageConstantRecord c => new PackageConstantReader(declaringClass, index, c),
+ _ => throw new ByteCodeException($"Invalid constant type: {record.GetType().Name}"),
+ };
+
+ }
+
+ ///
+ /// Base type for constant readers.
+ ///
+ ///
+ internal abstract class ConstantReader : ReaderBase, IConstantReader
+ where TRecord : ConstantRecord
+ {
+
+ readonly ushort index;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ protected ConstantReader(ClassReader declaringClass, ushort index, TRecord record) :
+ base(declaringClass, record)
+ {
+ this.index = index;
+ }
+
+ ///
+ /// Gets the index of the constant.
+ ///
+ public ushort Index => index;
+
+ ///
+ /// Returns true if the constant is considered loadable according to the JVM specification.
+ ///
+ public virtual bool IsLoadable => false;
+
+ ///
+ /// Gets the underlying constant being read.
+ ///
+ TRecord IConstantReader.Record => Record;
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ConstantReaderCollection.cs b/src/IKVM.ByteCode/Reading/ConstantReaderCollection.cs
new file mode 100644
index 0000000000..dc44a309c0
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ConstantReaderCollection.cs
@@ -0,0 +1,63 @@
+using System;
+
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of constants.
+ ///
+ internal sealed class ConstantReaderCollection : LazyReaderList
+ {
+
+ readonly ClassReader declaringClass;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal ConstantReaderCollection(ClassReader declaringClass, ConstantRecord[] records) :
+ base(declaringClass, records, 0)
+ {
+ this.declaringClass = declaringClass ?? throw new ArgumentNullException(nameof(declaringClass));
+ }
+
+ ///
+ /// Creates a new reader for the given record.
+ ///
+ ///
+ ///
+ ///
+ protected override IConstantReader CreateReader(int index, ConstantRecord record)
+ {
+ return record is not null ? ConstantReader.Read(declaringClass, (ushort)index, record) : null;
+ }
+
+ ///
+ /// Attempts to get the constant reader at the specified index.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public TReader Get(int index)
+ where TReader : class, IConstantReader
+ {
+ if (index == 0)
+ return null;
+
+ try
+ {
+ return this[index] as TReader ?? throw new ByteCodeException($"Invalid constant resolution. Reader at index {index} is not a {typeof(TReader).Name}.");
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ throw new ByteCodeException($"Invalid constant resolution. Reader at index {index} is not valid.", e);
+ }
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ConstantValueAttributeReader.cs b/src/IKVM.ByteCode/Reading/ConstantValueAttributeReader.cs
new file mode 100644
index 0000000000..71f6d074a0
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ConstantValueAttributeReader.cs
@@ -0,0 +1,37 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ConstantValueAttributeReader : AttributeReader
+ {
+
+ object value;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal ConstantValueAttributeReader(ClassReader declaringClass, AttributeInfoReader info, ConstantValueAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ public object Value => value ??= ResolveValue();
+
+ object ResolveValue() => DeclaringClass.Constants.Get(Record.ValueIndex) switch
+ {
+ LongConstantReader l => l.Value,
+ FloatConstantReader f => f.Value,
+ DoubleConstantReader d => d.Value,
+ IntegerConstantReader i => i.Value,
+ StringConstantReader s => s.Value,
+ _ => throw new ByteCodeException("Invalid constant type for constant attribute."),
+ };
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/DelegateLazyReaderList.cs b/src/IKVM.ByteCode/Reading/DelegateLazyReaderList.cs
new file mode 100644
index 0000000000..390811aa1d
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/DelegateLazyReaderList.cs
@@ -0,0 +1,40 @@
+using System;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Concrete lazy list implementation that creates readers lazily based on a delegate.
+ ///
+ ///
+ ///
+ internal sealed class DelegateLazyReaderList : LazyReaderList
+ where TReader : class
+ {
+
+ readonly Func create;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public DelegateLazyReaderList(ClassReader declaringClass, TSource[] sources, Func create) :
+ base(declaringClass, sources)
+ {
+ this.create = create ?? throw new ArgumentNullException(nameof(create));
+ }
+
+ ///
+ /// Creates the new reader.
+ ///
+ ///
+ ///
+ ///
+ protected override TReader CreateReader(int index, TSource source) => create(index, source);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/DeprecatedAttributeReader.cs b/src/IKVM.ByteCode/Reading/DeprecatedAttributeReader.cs
new file mode 100644
index 0000000000..e3c1d966d4
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/DeprecatedAttributeReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal class DeprecatedAttributeReader : AttributeReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal DeprecatedAttributeReader(ClassReader declaringClass, AttributeInfoReader info, DeprecatedAttributeRecord data) :
+ base(declaringClass, info, data)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/DoubleConstantReader.cs b/src/IKVM.ByteCode/Reading/DoubleConstantReader.cs
new file mode 100644
index 0000000000..78e4fbfb65
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/DoubleConstantReader.cs
@@ -0,0 +1,33 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class DoubleConstantReader : ConstantReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public DoubleConstantReader(ClassReader declaringClass, ushort index, DoubleConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the value of the constant.
+ ///
+ public double Value => Record.Value;
+
+ ///
+ /// Returns whether or not this constant is loadable.
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(45, 3);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/DynamicConstantReader.cs b/src/IKVM.ByteCode/Reading/DynamicConstantReader.cs
new file mode 100644
index 0000000000..b0e9d83b5e
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/DynamicConstantReader.cs
@@ -0,0 +1,32 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class DynamicConstantReader : ConstantReader
+ {
+
+ NameAndTypeConstantReader nameAndType;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public DynamicConstantReader(ClassReader declaringClass, ushort index, DynamicConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ public ushort BootstrapMethodAttributeIndex => Record.BootstrapMethodAttributeIndex;
+
+ public NameAndTypeConstantReader NameAndType => LazyGet(ref nameAndType, () => DeclaringClass.Constants.Get(Record.NameAndTypeIndex));
+
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(55, 0);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ElementValueAnnotationReader.cs b/src/IKVM.ByteCode/Reading/ElementValueAnnotationReader.cs
new file mode 100644
index 0000000000..f5987d5f47
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueAnnotationReader.cs
@@ -0,0 +1,31 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ElementValueAnnotationReader : ElementValueReader
+ {
+
+ AnnotationReader annotation;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueAnnotationReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the annotation included with this element value.
+ ///
+ public AnnotationReader Annotation => LazyGet(ref annotation, () => new AnnotationReader(DeclaringClass, ValueRecord.Annotation));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/ElementValueArrayReader.cs b/src/IKVM.ByteCode/Reading/ElementValueArrayReader.cs
new file mode 100644
index 0000000000..ef7a28b56b
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueArrayReader.cs
@@ -0,0 +1,28 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ElementValueArrayReader : ElementValueReader
+ {
+
+ ElementValueReaderCollection values;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueArrayReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ public ElementValueReaderCollection Values => LazyGet(ref values, () => new ElementValueReaderCollection(DeclaringClass, ValueRecord.Values));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ElementValueClassReader.cs b/src/IKVM.ByteCode/Reading/ElementValueClassReader.cs
new file mode 100644
index 0000000000..69a780d5da
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueClassReader.cs
@@ -0,0 +1,31 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ElementValueClassReader : ElementValueReader
+ {
+
+ Utf8ConstantReader clazz;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueClassReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the class included with this element value.
+ ///
+ public Utf8ConstantReader Class => LazyGet(ref clazz, () => DeclaringClass.Constants.Get(ValueRecord.ClassIndex));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/ElementValueConstantReader.cs b/src/IKVM.ByteCode/Reading/ElementValueConstantReader.cs
new file mode 100644
index 0000000000..1abc7f9c87
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueConstantReader.cs
@@ -0,0 +1,55 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ElementValueConstantReader : ElementValueReader
+ {
+
+ IConstantReader value;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueConstantReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the type of the element value.
+ ///
+ public ElementValueTag Tag => Record.Tag;
+
+ ///
+ /// Gets the constant value.
+ ///
+ public IConstantReader Value => LazyGet(ref value, ResolveValue);
+
+ ///
+ /// Gets the value of the constant element.
+ ///
+ ///
+ ///
+ IConstantReader ResolveValue() => Record.Tag switch
+ {
+ ElementValueTag.Byte => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Char => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Double => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Float => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Integer => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Long => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Short => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.Boolean => DeclaringClass.Constants.Get(ValueRecord.Index),
+ ElementValueTag.String => DeclaringClass.Constants.Get(ValueRecord.Index),
+ _ => throw new ByteCodeException($"Unknown element value constant tag '{Record.Tag}'.")
+ };
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/ElementValueEnumConstantReader.cs b/src/IKVM.ByteCode/Reading/ElementValueEnumConstantReader.cs
new file mode 100644
index 0000000000..46c3f9d31e
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueEnumConstantReader.cs
@@ -0,0 +1,31 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ElementValueEnumConstantReader : ElementValueReader
+ {
+
+ Utf8ConstantReader typeName;
+ Utf8ConstantReader constantName;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueEnumConstantReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ public Utf8ConstantReader TypeName => LazyGet(ref typeName, () => DeclaringClass.Constants.Get(ValueRecord.TypeNameIndex));
+
+ public Utf8ConstantReader ConstantName => LazyGet(ref constantName, () => DeclaringClass.Constants.Get(ValueRecord.ConstantNameIndex));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/ElementValuePairReaderCollection.cs b/src/IKVM.ByteCode/Reading/ElementValuePairReaderCollection.cs
new file mode 100644
index 0000000000..74b528ce5b
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValuePairReaderCollection.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of element value data.
+ ///
+ internal sealed class ElementValueKeyReaderCollection : LazyNamedReaderDictionary
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public ElementValueKeyReaderCollection(ClassReader declaringClass, ElementValuePairRecord[] records, int minIndex = 0) :
+ base(declaringClass, records, minIndex)
+ {
+
+ }
+
+ ///
+ /// Creates a new reader for the given record at the specified index.
+ ///
+ ///
+ ///
+ ///
+ protected override ElementValueReader CreateReader(int index, ElementValuePairRecord record)
+ {
+ return ElementValueReader.Resolve(DeclaringClass, record.Value);
+ }
+
+ ///
+ /// Gets the key for the given record and the specified index.
+ ///
+ ///
+ ///
+ ///
+ protected override string GetName(int index, ElementValuePairRecord record)
+ {
+ return DeclaringClass.Constants.Get(record.NameIndex).Value;
+ }
+
+ ///
+ /// Gets an enumerator over the items in this collection.
+ ///
+ ///
+ public new IEnumerator> GetEnumerator() => ((IEnumerable>)this).GetEnumerator();
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ElementValueReaderCollection.cs b/src/IKVM.ByteCode/Reading/ElementValueReaderCollection.cs
new file mode 100644
index 0000000000..50423c6bc2
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueReaderCollection.cs
@@ -0,0 +1,30 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of attribute data.
+ ///
+ internal sealed class ElementValueReaderCollection : LazyReaderList
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public ElementValueReaderCollection(ClassReader delcaringClass, ElementValueRecord[] records) :
+ base(delcaringClass, records)
+ {
+
+ }
+
+ protected override ElementValueReader CreateReader(int index, ElementValueRecord record)
+ {
+ return ElementValueReader.Resolve(DeclaringClass, record);
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ElementValueValueReader.cs b/src/IKVM.ByteCode/Reading/ElementValueValueReader.cs
new file mode 100644
index 0000000000..eab07860dd
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ElementValueValueReader.cs
@@ -0,0 +1,64 @@
+using System;
+
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal abstract class ElementValueReader : ReaderBase
+ {
+
+ ///
+ /// Resolves the given element value record to a reader type.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public static ElementValueReader Resolve(ClassReader declaringClass, ElementValueRecord record) => record.Value switch
+ {
+ ElementValueAnnotationValueRecord => new ElementValueAnnotationReader(declaringClass, record),
+ ElementValueArrayValueRecord => new ElementValueArrayReader(declaringClass, record),
+ ElementValueClassValueRecord => new ElementValueClassReader(declaringClass, record),
+ ElementValueConstantValueRecord => new ElementValueConstantReader(declaringClass, record),
+ ElementValueEnumConstantValueRecord => new ElementValueEnumConstantReader(declaringClass, record),
+ _ => throw new ByteCodeException("Cannot resolve element value reader."),
+ };
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ protected ElementValueReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ }
+
+ internal abstract class ElementValueReader : ElementValueReader
+ where TValueRecord : ElementValueValueRecord
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ protected ElementValueReader(ClassReader declaringClass, ElementValueRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the underlying value record.
+ ///
+ public TValueRecord ValueRecord => (TValueRecord)Record.Value;
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/EnclosingMethodAttributeReader.cs b/src/IKVM.ByteCode/Reading/EnclosingMethodAttributeReader.cs
new file mode 100644
index 0000000000..83f98567c5
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/EnclosingMethodAttributeReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class EnclosingMethodAttributeReader : AttributeReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal EnclosingMethodAttributeReader(ClassReader declaringClass, AttributeInfoReader info, EnclosingMethodAttributeRecord data) :
+ base(declaringClass, info, data)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/ExceptionsAttributeReader.cs b/src/IKVM.ByteCode/Reading/ExceptionsAttributeReader.cs
new file mode 100644
index 0000000000..5b115ba3fc
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/ExceptionsAttributeReader.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class ExceptionsAttributeReader : AttributeReader
+ {
+
+ DelegateLazyReaderList exceptions;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal ExceptionsAttributeReader(ClassReader declaringClass, AttributeInfoReader info, ExceptionsAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ ///
+ /// Gets the names of the exceptions.
+ ///
+ public IReadOnlyList Exceptions => LazyGet(ref exceptions, () => new DelegateLazyReaderList(DeclaringClass, Record.ExceptionsIndexes, (_, index) => DeclaringClass.Constants.Get(index)));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/FieldOrMethodReader.cs b/src/IKVM.ByteCode/Reading/FieldOrMethodReader.cs
new file mode 100644
index 0000000000..2edbe9e5bd
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/FieldOrMethodReader.cs
@@ -0,0 +1,40 @@
+namespace IKVM.ByteCode.Reading
+{
+
+ internal abstract class FieldOrMethodReader : ReaderBase
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ protected FieldOrMethodReader(ClassReader declaringClass, TRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the access flags of this field or method.
+ ///
+ public abstract AccessFlag AccessFlags { get; }
+
+ ///
+ /// Gets the name of this field or method.
+ ///
+ public abstract Utf8ConstantReader Name { get; }
+
+ ///
+ /// Gets the descriptor of this field or method.
+ ///
+ public abstract Utf8ConstantReader Descriptor { get; }
+
+ ///
+ /// Gets the attributes of this field or method.
+ ///
+ public abstract AttributeReaderCollection Attributes { get; }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/FieldReader.cs b/src/IKVM.ByteCode/Reading/FieldReader.cs
new file mode 100644
index 0000000000..d86faf7b21
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/FieldReader.cs
@@ -0,0 +1,48 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal class FieldReader : FieldOrMethodReader
+ {
+
+ Utf8ConstantReader name;
+ Utf8ConstantReader descriptor;
+ AttributeReaderCollection attributes;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal FieldReader(ClassReader declaringClass, FieldRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the access flags of the field.
+ ///
+ public override AccessFlag AccessFlags => Record.AccessFlags;
+
+ ///
+ /// Gets the name of the field.
+ ///
+ public override Utf8ConstantReader Name => LazyGet(ref name, () => DeclaringClass.Constants.Get(Record.NameIndex));
+
+ ///
+ /// Gets the descriptor of the field.
+ ///
+ public override Utf8ConstantReader Descriptor => LazyGet(ref descriptor, () => DeclaringClass.Constants.Get(Record.DescriptorIndex));
+
+ ///
+ /// Gets the attributes of the field.
+ ///
+ public override AttributeReaderCollection Attributes => LazyGet(ref attributes, () => new AttributeReaderCollection(DeclaringClass, Record.Attributes));
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/FieldReaderCollection.cs b/src/IKVM.ByteCode/Reading/FieldReaderCollection.cs
new file mode 100644
index 0000000000..a257f86613
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/FieldReaderCollection.cs
@@ -0,0 +1,47 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of fields.
+ ///
+ internal sealed class FieldReaderCollection : LazyNamedReaderDictionary
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal FieldReaderCollection(ClassReader declaringClass, FieldRecord[] records) :
+ base(declaringClass, records)
+ {
+
+ }
+
+ ///
+ /// Creates a new field reader.
+ ///
+ ///
+ ///
+ ///
+ protected override FieldReader CreateReader(int index, FieldRecord record)
+ {
+ return new FieldReader(DeclaringClass, record);
+ }
+
+ ///
+ /// Gets the key for the specified record.
+ ///
+ ///
+ ///
+ ///
+ protected override string GetName(int index, FieldRecord record)
+ {
+ return DeclaringClass.Constants.Get(record.NameIndex).Value;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/FieldrefConstantReader.cs b/src/IKVM.ByteCode/Reading/FieldrefConstantReader.cs
new file mode 100644
index 0000000000..689d36bc45
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/FieldrefConstantReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal class FieldrefConstantReader : RefConstantReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public FieldrefConstantReader(ClassReader declaringClass, ushort index, FieldrefConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/FloatConstantReader.cs b/src/IKVM.ByteCode/Reading/FloatConstantReader.cs
new file mode 100644
index 0000000000..6cd95da0c5
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/FloatConstantReader.cs
@@ -0,0 +1,33 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class FloatConstantReader : ConstantReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public FloatConstantReader(ClassReader declaringClass, ushort index, FloatConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the value of the constant.
+ ///
+ public float Value => Record.Value;
+
+ ///
+ /// Returns true if this constant is loadable.
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(45, 3);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/IConstantReader.cs b/src/IKVM.ByteCode/Reading/IConstantReader.cs
new file mode 100644
index 0000000000..d9f1b8b516
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/IConstantReader.cs
@@ -0,0 +1,38 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Interface supported by all of the constant readers.
+ ///
+ internal interface IConstantReader
+ {
+
+ ///
+ /// Gets the index of this constant.
+ ///
+ ushort Index { get; }
+
+ ///
+ /// Returns true if this constant is loadable.
+ ///
+ bool IsLoadable { get; }
+
+ }
+
+ ///
+ /// Interface supported by all of the constant readers.
+ ///
+ internal interface IConstantReader : IConstantReader
+ where TRecord : ConstantRecord
+ {
+
+ ///
+ /// Gets the record.
+ ///
+ TRecord Record { get; }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/IRefConstantReader.cs b/src/IKVM.ByteCode/Reading/IRefConstantReader.cs
new file mode 100644
index 0000000000..1e97bffcef
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/IRefConstantReader.cs
@@ -0,0 +1,24 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Interface supported by all of the readers for Ref constants.
+ ///
+ internal interface IRefConstantReader : IConstantReader
+ {
+
+ ///
+ /// Gets the class name of the reference.
+ ///
+ ClassConstantReader Class { get; }
+
+ ///
+ /// Gets the name and type reference.
+ ///
+ NameAndTypeConstantReader NameAndType { get; }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/InnerClassesAttributeItemReader.cs b/src/IKVM.ByteCode/Reading/InnerClassesAttributeItemReader.cs
new file mode 100644
index 0000000000..393ac5bbe9
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InnerClassesAttributeItemReader.cs
@@ -0,0 +1,48 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class InnerClassesAttributeItemReader : ReaderBase
+ {
+
+ ClassConstantReader innerClass;
+ ClassConstantReader outerClass;
+ Utf8ConstantReader innerName;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public InnerClassesAttributeItemReader(ClassReader declaringClass, InnerClassesAttributeItemRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the name of the inner class.
+ ///
+ public ClassConstantReader InnerClass => LazyGet(ref innerClass, () => DeclaringClass.Constants.Get(Record.InnerClassIndex));
+
+ ///
+ /// Gets the name of the outer class.
+ ///
+ public ClassConstantReader OuterClass => LazyGet(ref outerClass, () => DeclaringClass.Constants.Get(Record.OuterClassIndex));
+
+ ///
+ /// Gets the inner name.
+ ///
+ public Utf8ConstantReader InnerName => LazyGet(ref innerName, () => DeclaringClass.Constants.Get(Record.InnerNameIndex));
+
+ ///
+ /// Gets the access flags of the inner class.
+ ///
+ public AccessFlag InnerClassAccessFlags => Record.InnerClassAccessFlags;
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/InnerClassesAttributeReader.cs b/src/IKVM.ByteCode/Reading/InnerClassesAttributeReader.cs
new file mode 100644
index 0000000000..b26ebcff26
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InnerClassesAttributeReader.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class InnerClassesAttributeReader : AttributeReader
+ {
+
+ IReadOnlyList items;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public InnerClassesAttributeReader(ClassReader declaringClass, AttributeInfoReader info, InnerClassesAttributeRecord record) :
+ base(declaringClass, info, record)
+ {
+
+ }
+
+ ///
+ /// Gets the items on the attribute.
+ ///
+ public IReadOnlyList Items => LazyGet(ref items, () => new DelegateLazyReaderList(DeclaringClass, Record.Items, (_, record) => new InnerClassesAttributeItemReader(DeclaringClass, record)));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/IntegerConstantReader.cs b/src/IKVM.ByteCode/Reading/IntegerConstantReader.cs
new file mode 100644
index 0000000000..a0c172008b
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/IntegerConstantReader.cs
@@ -0,0 +1,31 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class IntegerConstantReader : ConstantReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public IntegerConstantReader(ClassReader declaringClass, ushort index, IntegerConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the value of the constant.
+ ///
+ public int Value => Record.Value;
+
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(45, 3);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/InterfaceMethodrefConstantReader.cs b/src/IKVM.ByteCode/Reading/InterfaceMethodrefConstantReader.cs
new file mode 100644
index 0000000000..c1a9d9fa9d
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InterfaceMethodrefConstantReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class InterfaceMethodrefConstantReader : RefConstantReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public InterfaceMethodrefConstantReader(ClassReader declaringClass, ushort index, InterfaceMethodrefConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/InterfaceReader.cs b/src/IKVM.ByteCode/Reading/InterfaceReader.cs
new file mode 100644
index 0000000000..05b01b1f76
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InterfaceReader.cs
@@ -0,0 +1,31 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class InterfaceReader : ReaderBase
+ {
+
+ ClassConstantReader clazz;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal InterfaceReader(ClassReader declaringClass, InterfaceRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the name of the interface.
+ ///
+ public ClassConstantReader Class => LazyGet(ref clazz, () => DeclaringClass.Constants.Get(Record.ClassIndex));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/InterfaceReaderCollection.cs b/src/IKVM.ByteCode/Reading/InterfaceReaderCollection.cs
new file mode 100644
index 0000000000..023d3c3126
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InterfaceReaderCollection.cs
@@ -0,0 +1,46 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Lazy init collection of method data.
+ ///
+ internal sealed class InterfaceReaderCollection : LazyNamedReaderDictionary
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ internal InterfaceReaderCollection(ClassReader declaringClass, InterfaceRecord[] records) :
+ base(declaringClass, records)
+ {
+
+ }
+
+ ///
+ /// Creates a new interface reader.
+ ///
+ ///
+ ///
+ protected override InterfaceReader CreateReader(int index, InterfaceRecord record)
+ {
+ return new InterfaceReader(DeclaringClass, record);
+ }
+
+ ///
+ /// Gets the name of the interface.
+ ///
+ ///
+ ///
+ ///
+ protected override string GetName(int index, InterfaceRecord record)
+ {
+ return DeclaringClass.Constants.Get(record.ClassIndex).Name.Value;
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/InvokeDynamicConstantReader.cs b/src/IKVM.ByteCode/Reading/InvokeDynamicConstantReader.cs
new file mode 100644
index 0000000000..6fb3584726
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/InvokeDynamicConstantReader.cs
@@ -0,0 +1,37 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class InvokeDynamicConstantReader : ConstantReader
+ {
+
+ NameAndTypeConstantReader nameAndType;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public InvokeDynamicConstantReader(ClassReader owner, ushort index, InvokeDynamicConstantRecord record) :
+ base(owner, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the index into the BootstrapMethod table that is referenced by this constant.
+ ///
+ public ushort BootstrapMethodAttributeIndex => Record.BootstrapMethodAttributeIndex;
+
+ ///
+ /// Gets the name of the InvokeDynamic constant.
+ ///
+ public NameAndTypeConstantReader NameAndType => LazyGet(ref nameAndType, () => DeclaringClass.Constants.Get(Record.NameAndTypeIndex));
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LazyNamedReaderDictionary.cs b/src/IKVM.ByteCode/Reading/LazyNamedReaderDictionary.cs
new file mode 100644
index 0000000000..17fb0dbe96
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LazyNamedReaderDictionary.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Base implementation of a dictionary that generates reader instances on demand.
+ ///
+ ///
+ ///
+ internal abstract class LazyNamedReaderDictionary : IReadOnlyDictionary
+ where TReader : class
+ {
+
+ readonly ClassReader declaringClass;
+ readonly TRecord[] records;
+ readonly int minIndex;
+
+ TReader[] readers;
+ string[] names;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public LazyNamedReaderDictionary(ClassReader declaringClass, TRecord[] records, int minIndex = 0)
+ {
+ this.declaringClass = declaringClass ?? throw new ArgumentNullException(nameof(declaringClass));
+ this.records = records ?? throw new ArgumentNullException(nameof(records));
+ this.minIndex = minIndex;
+ }
+
+ ///
+ /// Gets the underlying records that make up the list.
+ ///
+ protected IReadOnlyList Records => records;
+
+ ///
+ /// Creates the appropriate reader.
+ ///
+ ///
+ ///
+ ///
+ protected abstract TReader CreateReader(int index, TRecord record);
+
+ ///
+ /// Gets the name for the given record.
+ ///
+ ///
+ ///
+ ///
+ protected abstract string GetName(int index, TRecord record);
+
+ ///
+ /// Resolves the specified reader at the given index.
+ ///
+ ///
+ TReader ResolveReader(int index)
+ {
+ if (index < minIndex || index >= records.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ // initialize cache if not initialized
+ if (readers == null)
+ Interlocked.CompareExchange(ref readers, new TReader[records.Length], null);
+
+ // consult cache
+ if (readers[index] is TReader reader)
+ return reader;
+
+ // atomic set, only one winner
+ Interlocked.CompareExchange(ref readers[index], CreateReader(index, records[index]), null);
+ return readers[index];
+ }
+
+ ///
+ /// Resolves the specified name at the given index.
+ ///
+ ///
+ ///
+ ///
+ string ResolveName(int index)
+ {
+ if (index < minIndex || index >= records.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ // initialize cache if not initialized
+ if (names == null)
+ Interlocked.CompareExchange(ref names, new string[records.Length], null);
+
+ // consult cache
+ if (names[index] is string name)
+ return name;
+
+ // atomic set, only one winner
+ Interlocked.CompareExchange(ref names[index], GetName(index, records[index]), null);
+ return names[index];
+ }
+
+ ///
+ /// Gets the class that declared this list.
+ ///
+ public ClassReader DeclaringClass => declaringClass;
+
+ ///
+ /// Gets the reader at the specified index.
+ ///
+ ///
+ ///
+ public TReader this[int index] => ResolveReader(index);
+
+ ///
+ /// Gets the reader with the specified name.
+ ///
+ ///
+ ///
+ public TReader this[string name] => Enumerable.Range(minIndex, records.Length).Where(i => ResolveName(i) == name).Select(ResolveReader).FirstOrDefault() ?? throw new KeyNotFoundException();
+
+ ///
+ /// Gets the count of readers.
+ ///
+ public int Count => records.Length;
+
+ ///
+ /// Attempts to get the value at the specified index, or returns the default value if out of range.
+ ///
+ ///
+ ///
+ ///
+ public bool TryGet(int index, out TReader value)
+ {
+ value = default;
+
+ if (index < minIndex || index >= records.Length)
+ return false;
+
+ value = this[index];
+ return true;
+ }
+
+ ///
+ /// Attempts to get the value at the specified index, or returns the default value if out of range.
+ ///
+ ///
+ ///
+ ///
+ public bool TryGet(string name, out TReader value) => (value = Enumerable.Range(minIndex, records.Length).Where(i => ResolveName(i) == name).Select(ResolveReader).FirstOrDefault()) != null;
+
+ ///
+ /// Returns true if the collection contains a reader with the specified name.
+ ///
+ ///
+ ///
+ public bool Contains(string name) => Enumerable.Range(minIndex, records.Length).Any(i => ResolveName(i) == name);
+
+ ///
+ /// Gets an enumerator over each reader.
+ ///
+ ///
+ public IEnumerator GetEnumerator() => Enumerable.Range(minIndex, records.Length).Select(i => this[i]).GetEnumerator();
+
+ ///
+ /// Gets an enumerator over each reader.
+ ///
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ ///
+ /// Gets an enumerator over each reader.
+ ///
+ ///
+ IEnumerator> IEnumerable>.GetEnumerator() => Enumerable.Range(minIndex, records.Length).Select(i => new KeyValuePair(ResolveName(i), ResolveReader(i))).GetEnumerator();
+
+ ///
+ /// Gets all of the available keys.
+ ///
+ IEnumerable IReadOnlyDictionary.Keys => Enumerable.Range(minIndex, records.Length).Select(ResolveName);
+
+ ///
+ /// Gets all of the available values.
+ ///
+ IEnumerable IReadOnlyDictionary.Values => Enumerable.Range(minIndex, records.Length).Select(ResolveReader);
+
+ ///
+ /// Attempts to retrieve the reader with the specified key.
+ ///
+ ///
+ ///
+ ///
+ bool IReadOnlyDictionary.TryGetValue(string key, out TReader value) => TryGet(key, out value);
+
+ ///
+ /// Returns true if the collection contains the specified key.
+ ///
+ ///
+ ///
+ bool IReadOnlyDictionary.ContainsKey(string key) => Contains(key);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LazyReaderList.cs b/src/IKVM.ByteCode/Reading/LazyReaderList.cs
new file mode 100644
index 0000000000..2d66ecb31f
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LazyReaderList.cs
@@ -0,0 +1,103 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ ///
+ /// Base implementation of a list that generates reader instances on demand.
+ ///
+ ///
+ ///
+ internal abstract class LazyReaderList : IReadOnlyList
+ where TReader : class
+ {
+
+ readonly ClassReader declaringClass;
+ readonly TRecord[] records;
+ readonly uint minIndex;
+
+ TReader[] readers;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ public LazyReaderList(ClassReader declaringClass, TRecord[] records, uint minIndex = 0)
+ {
+ this.declaringClass = declaringClass ?? throw new ArgumentNullException(nameof(declaringClass));
+ this.records = records ?? throw new ArgumentNullException(nameof(records));
+ this.minIndex = minIndex;
+ }
+
+ ///
+ /// Gets the underlying records that make up the list.
+ ///
+ protected IReadOnlyList Records => records;
+
+ ///
+ /// Creates the appropriate reader.
+ ///
+ ///
+ ///
+ ///
+ protected abstract TReader CreateReader(int index, TRecord record);
+
+ ///
+ /// Resolves the specified reader at the given index.
+ ///
+ ///
+ TReader ResolveReader(int index)
+ {
+ if (index < minIndex || index >= records.Length)
+ throw new ArgumentOutOfRangeException(nameof(index));
+
+ // initialize cache if not initialized
+ if (readers == null)
+ Interlocked.CompareExchange(ref readers, new TReader[records.Length], null);
+
+ // consult cache
+ if (readers[index] is TReader reader)
+ return reader;
+
+ // atomic set, only one winner
+ Interlocked.CompareExchange(ref readers[index], CreateReader(index, records[index]), null);
+ return readers[index];
+ }
+
+ ///
+ /// Gets the class that declared this list.
+ ///
+ public ClassReader DeclaringClass => declaringClass;
+
+ ///
+ /// Gets the reader at the specified index.
+ ///
+ ///
+ ///
+ public TReader this[int index] => ResolveReader(index);
+
+ ///
+ /// Gets the count of readers.
+ ///
+ public int Count => records.Length;
+
+ ///
+ /// Gets an enumerator over each reader.
+ ///
+ ///
+ public IEnumerator GetEnumerator() => Enumerable.Range(0, records.Length).Select(i => this[i]).GetEnumerator();
+
+ ///
+ /// Gets an enumerator over each reader.
+ ///
+ ///
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LineNumberTableAttributeReader.cs b/src/IKVM.ByteCode/Reading/LineNumberTableAttributeReader.cs
new file mode 100644
index 0000000000..35d5977a06
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LineNumberTableAttributeReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class LineNumberTableAttributeReader : AttributeReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal LineNumberTableAttributeReader(ClassReader declaringClass, AttributeInfoReader info, LineNumberTableAttributeRecord data) :
+ base(declaringClass, info, data)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LocalVariableTableAttributeReader.cs b/src/IKVM.ByteCode/Reading/LocalVariableTableAttributeReader.cs
new file mode 100644
index 0000000000..24876f5b9a
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LocalVariableTableAttributeReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class LocalVariableTableAttributeReader : AttributeReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal LocalVariableTableAttributeReader(ClassReader declaringClass, AttributeInfoReader info, LocalVariableTableAttributeRecord data) :
+ base(declaringClass, info, data)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LocalVariableTypeTableAttributeReader.cs b/src/IKVM.ByteCode/Reading/LocalVariableTypeTableAttributeReader.cs
new file mode 100644
index 0000000000..6b0794f772
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LocalVariableTypeTableAttributeReader.cs
@@ -0,0 +1,23 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class LocalVariableTypeTableAttributeReader : AttributeReader
+ {
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ internal LocalVariableTypeTableAttributeReader(ClassReader declaringClass, AttributeInfoReader info, LocalVariableTypeTableAttributeRecord data) :
+ base(declaringClass, info, data)
+ {
+
+ }
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/LongConstantReader.cs b/src/IKVM.ByteCode/Reading/LongConstantReader.cs
new file mode 100644
index 0000000000..4cf3ee55fb
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/LongConstantReader.cs
@@ -0,0 +1,34 @@
+using IKVM.ByteCode.Parsing;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class LongConstantReader : ConstantReader
+ {
+
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public LongConstantReader(ClassReader declaringClass, ushort index, LongConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the value of the constant.
+ ///
+ public long Value => Record.Value;
+
+ ///
+ /// Returns true if this constant is loadable.
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(45, 3);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/MethodHandleConstantReader.cs b/src/IKVM.ByteCode/Reading/MethodHandleConstantReader.cs
new file mode 100644
index 0000000000..0c1d7bfe30
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/MethodHandleConstantReader.cs
@@ -0,0 +1,42 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class MethodHandleConstantReader : ConstantReader
+ {
+
+ IRefConstantReader reference;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ ///
+ ///
+ public MethodHandleConstantReader(ClassReader declaringClass, ushort index, MethodHandleConstantRecord record) :
+ base(declaringClass, index, record)
+ {
+
+ }
+
+ ///
+ /// Gets the kind of this reference.
+ ///
+ public ReferenceKind ReferenceKind => Record.Kind;
+
+ ///
+ /// Gets the constant refered to by this MethodHandle.
+ ///
+ public IRefConstantReader Reference => LazyGet(ref reference, () => DeclaringClass.Constants.Get(Record.Index));
+
+ ///
+ /// Returns true if this constant type is loadable.
+ ///
+ public override bool IsLoadable => DeclaringClass.Version >= new ClassFormatVersion(51, 0);
+
+ }
+
+}
diff --git a/src/IKVM.ByteCode/Reading/MethodParametersAttributeParameterReader.cs b/src/IKVM.ByteCode/Reading/MethodParametersAttributeParameterReader.cs
new file mode 100644
index 0000000000..f6b070bbce
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/MethodParametersAttributeParameterReader.cs
@@ -0,0 +1,35 @@
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class MethodParametersAttributeParameterReader : ReaderBase
+ {
+
+ Utf8ConstantReader name;
+
+ ///
+ /// Initializes a new instance.
+ ///
+ ///
+ public MethodParametersAttributeParameterReader(ClassReader declaringClass, MethodParametersAttributeParameterRecord record) :
+ base(declaringClass, record)
+ {
+
+ }
+
+ ///
+ /// Gets the name of the parameters.
+ ///
+ public Utf8ConstantReader Name => LazyGet(ref name, () => DeclaringClass.Constants.Get(Record.NameIndex));
+
+ ///
+ /// Gets the access flags of the parameter.
+ ///
+ public AccessFlag AccessFlags => Record.AccessFlags;
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/IKVM.ByteCode/Reading/MethodParametersAttributeReader.cs b/src/IKVM.ByteCode/Reading/MethodParametersAttributeReader.cs
new file mode 100644
index 0000000000..cd0e72ff96
--- /dev/null
+++ b/src/IKVM.ByteCode/Reading/MethodParametersAttributeReader.cs
@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+
+using IKVM.ByteCode.Parsing;
+
+using static IKVM.ByteCode.Util;
+
+namespace IKVM.ByteCode.Reading
+{
+
+ internal sealed class MethodParametersAttributeReader : AttributeReader
+ {
+
+ IReadOnlyList parameters;
+
+ ///