diff --git a/.github/actions/build-graalvm/action.yml b/.github/actions/build-graalvm/action.yml new file mode 100644 index 000000000000..245aa36cdf12 --- /dev/null +++ b/.github/actions/build-graalvm/action.yml @@ -0,0 +1,66 @@ +name: Build GraalVM JDK +description: 'Build GraalVM JDK and set up environment for testing' + +inputs: + native-images: + description: 'Internal GraalVM native images to build' + required: false + default: 'native-image' + components: + description: 'Internal GraalVM components to build' + required: false + default: 'Native Image' + java-version: + description: 'Java version to use' + required: false + default: '' + +runs: + using: 'composite' + steps: + - name: Set up environment variables + shell: bash + run: | + echo "GRAALVM_HOME=${{ github.workspace }}/graalvm" >> ${GITHUB_ENV} + echo "LABSJDK_HOME=${{ github.workspace }}/labsjdk" >> ${GITHUB_ENV} + echo "MX_GIT_CACHE=refcache" >> ${GITHUB_ENV} + echo "MX_PATH=${{ github.workspace }}/mx" >> ${GITHUB_ENV} + echo "MX_PYTHON=python3.8" >> ${GITHUB_ENV} + echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} + # Workaround testsuite locale issue + echo "LANG=en_US.UTF-8" >> ${GITHUB_ENV} + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx + ref: ${{ env.MX_VERSION }} + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Update mx cache + uses: actions/cache@v4 + with: + path: ~/.mx + key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} + restore-keys: ${{ runner.os }}-mx- + - name: Fetch LabsJDK + shell: bash + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-21 --to jdk-dl --alias ${LABSJDK_HOME} + - name: Build GraalVM JDK + shell: bash + run: | + cd substratevm + ${MX_PATH}/mx --java-home=${LABSJDK_HOME} --native-images="${{ inputs.native-images }}" --components="${{ inputs.components }}" build + ln -s $(${MX_PATH}/mx --java-home=${LABSJDK_HOME} --native-images="${{ inputs.native-images }}" --components="${{ inputs.components }}" graalvm-home) ${GRAALVM_HOME} + ${GRAALVM_HOME}/bin/native-image --version + - name: Set up JAVA_HOME + if: ${{ inputs.java-version }} != '' + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: '${{ inputs.java-version }}' + \ No newline at end of file diff --git a/.github/workflows/cdt-inspect.yml b/.github/workflows/cdt-inspect.yml new file mode 100644 index 000000000000..9ea51c7c49de --- /dev/null +++ b/.github/workflows/cdt-inspect.yml @@ -0,0 +1,92 @@ +# +# Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +# Intergation test of CDT with Inspector backend. +name: Weekly CDT Inspector + +on: + schedule: + - cron: "30 2 * * 2,5" # Tuesday and Friday at 2:30 + +env: + JAVA_HOME: ${{ github.workspace }}/jdk + JDK_VERSION: "latest" + MX_PATH: ${{ github.workspace }}/mx + SE_SKIP_DRIVER_IN_PATH: "true" + +jobs: + build: + + runs-on: ubuntu-latest + + if: github.repository == 'oracle/graal' + + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + with: + path: ${{ github.workspace }}/graal + - name: Checkout oracle/graaljs + uses: actions/checkout@v4 + with: + repository: oracle/graaljs + sparse-checkout: | + graal-js + path: ${{ github.workspace }}/js + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx.git + ref: master + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Fetch LabsJDK + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-${JDK_VERSION} --to jdk-dl --alias ${JAVA_HOME} + - run: | + cd ${{ github.workspace }}/graal/vm + ${MX_PATH}/mx --dy /tools,graal-js build + cd tests/gh_workflows/CDTInspectorTest + mvn -q compile + mvn -q exec:exec -Dtestargs="${{ github.workspace }}/graal/sdk/latest_graalvm_home/bin/js scripts/StepTest.js" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e3449828a294..67e6f60be515 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,3 +1,43 @@ +# +# Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# name: GraalVM Gate on: @@ -37,19 +77,21 @@ concurrency: env: JAVA_HOME: ${{ github.workspace }}/jdk - JDT: builtin LANG: en_US.UTF-8 MX_GIT_CACHE: refcache MX_PATH: ${{ github.workspace }}/mx MX_PYTHON: python3.8 + # Enforce experimental option checking in CI (GR-47922) + NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL: "true" permissions: contents: read # to fetch code (actions/checkout) jobs: - build-graalvm: + build-graalvm-linux: name: /${{ matrix.env.PRIMARY }} ${{ matrix.env.GATE_TAGS }} JDK${{ matrix.env.JDK_VERSION }} runs-on: ubuntu-20.04 + timeout-minutes: 60 strategy: fail-fast: false matrix: @@ -60,7 +102,7 @@ jobs: GATE_TAGS: "style,fullbuild,test" PRIMARY: "compiler" - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "build,bootstraplite" PRIMARY: "compiler" # /espresso @@ -74,16 +116,16 @@ jobs: GATE_TAGS: "style,fullbuild" PRIMARY: "substratevm" - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "build,helloworld,native_unittests" PRIMARY: "substratevm" PIP_PACKAGES: "jsonschema==4.6.1" - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "build,debuginfotest" PRIMARY: "substratevm" - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "hellomodule" PRIMARY: "substratevm" # /sulong @@ -98,7 +140,7 @@ jobs: PRIMARY: "truffle" # /vm - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "build,sulong" GATE_OPTS: "--no-warning-as-error" PRIMARY: "vm" @@ -107,7 +149,7 @@ jobs: DISABLE_POLYGLOT: true DISABLE_LIBPOLYGLOT: true - env: - JDK_VERSION: "21" + JDK_VERSION: "latest" GATE_TAGS: "build" GATE_OPTS: "--no-warning-as-error" PRIMARY: "vm" @@ -115,29 +157,29 @@ jobs: NATIVE_IMAGES: "lib:jvmcicompiler,native-image,lib:native-image-agent,lib:native-image-diagnostics-agent,polyglot" WITHOUT_VCS: true env: + JDT: builtin # Compile with ECJ (and javac) as part of gate runs tagged with 'fullbuild' MX_RUNS_DEBUG: ${{ contains(matrix.env.GATE_TAGS, 'debug') || matrix.env.GATE_TAGS == '' }} MX_RUNS_STYLE: ${{ contains(matrix.env.GATE_TAGS, 'style') || matrix.env.GATE_TAGS == '' }} steps: - name: Checkout oracle/graal - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} # Lock ref to current branch to avoid fetching others fetch-depth: "${{ env.MX_RUNS_STYLE && '0' || '1' }}" # The style gate needs the full commit history for checking copyright years - name: Determine mx version run: echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} - name: Checkout graalvm/mx - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: graalvm/mx.git ref: ${{ env.MX_VERSION }} - fetch-depth: 1 path: ${{ env.MX_PATH }} - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.8' - name: Update mx cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.mx key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} @@ -157,9 +199,7 @@ jobs: if: ${{ env.MX_RUNS_STYLE == 'true' }} run: | sudo apt install python3-pip python-setuptools - sudo pip install ninja_syntax$(jq -r '.pip.ninja_syntax' common.json) - sudo pip install lazy-object-proxy$(jq -r '.pip["lazy-object-proxy"]' common.json) - sudo pip install pylint$(jq -r '.pip.pylint' common.json) + sudo pip install $(jq -r '[.pip | to_entries[] | join("")] | join(" ")' common.json) - name: Install additional pip packages if: ${{ matrix.env.PIP_PACKAGES != '' }} run: ${MX_PYTHON} -m pip install ${{ matrix.env.PIP_PACKAGES }} @@ -183,3 +223,48 @@ jobs: env: ${{ matrix.env }} run: ${MX_PATH}/mx --primary-suite-path ${PRIMARY} --java-home=${JAVA_HOME} gate --strict-mode ${{ matrix.env.GATE_OPTS }} if: ${{ matrix.env.GATE_TAGS == '' }} + build-graalvm-windows: + name: /substratevm on Windows + runs-on: windows-2022 + timeout-minutes: 60 + env: + MX_PYTHON: 'python' + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} # Lock ref to current branch to avoid fetching others + - name: Determine mx version + shell: bash + run: echo "MX_VERSION=$(jq -r '.mx_version' common.json)" >> ${GITHUB_ENV} + - name: Checkout graalvm/mx + uses: actions/checkout@v4 + with: + repository: graalvm/mx.git + ref: ${{ env.MX_VERSION }} + path: ${{ env.MX_PATH }} + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.8' + - name: Fetch LabsJDK + shell: bash + run: | + mkdir jdk-dl + ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-latest --to jdk-dl --alias ${JAVA_HOME} + - name: Build GraalVM via cmd.exe + shell: cmd + run: | + call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsall.bat" x64 + call ${{ env.MX_PATH }}\mx.cmd -p substratevm --native-images=native-image --components="Native Image" build + call ${{ env.MX_PATH }}\mx.cmd -p substratevm --native-images=native-image --components="Native Image" graalvm-home > graalvm-home-with-forward-slashes.txt + set /p GRAALVM_HOME=>%GITHUB_PATH% + echo GRAALVM_HOME=%GRAALVM_HOME%>>%GITHUB_ENV% + - name: Test GraalVM + run: | + native-image --version + native-image -m jdk.httpserver + diff --git a/.github/workflows/micronaut.yml b/.github/workflows/micronaut.yml new file mode 100644 index 000000000000..4077f457eb3a --- /dev/null +++ b/.github/workflows/micronaut.yml @@ -0,0 +1,89 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Nightly Micronaut Tests + +on: + push: + paths: + - '.github/workflows/micronaut.yml' + pull_request: + paths: + - '.github/workflows/micronaut.yml' + schedule: + - cron: '0 2 * * *' + workflow_dispatch: + +env: + MICRONAUT_CORE_PATH: ${{ github.workspace }}/micronaut-core + MICRONAUT_JAVA_VERSION: 21 + # Enforce experimental option checking in CI (GR-47922) + NATIVE_IMAGE_EXPERIMENTAL_OPTIONS_ARE_FATAL: 'true' + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-micronaut: + name: Native Tests + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + java-version: ${{ env.MICRONAUT_JAVA_VERSION }} + - name: Run nativeTest in Micronaut launch project + run: | + curl --fail --silent --location --retry 3 --max-time 10 --output demo.zip --request GET 'https://launch.micronaut.io/create/default/com.example.demo?lang=JAVA&build=GRADLE&test=JUNIT&javaVersion=JDK_${{ env.MICRONAUT_JAVA_VERSION }}' + unzip demo.zip + cd demo + ./gradlew nativeTest + - name: Checkout micronaut-projects/micronaut-core + uses: actions/checkout@v4 + with: + repository: micronaut-projects/micronaut-core + path: ${{ env.MICRONAUT_CORE_PATH }} + - name: Run nativeTest in micronaut-core + run: | + cd ${{ env.MICRONAUT_CORE_PATH }} + ./gradlew nativeTest diff --git a/.github/workflows/quarkus.yml b/.github/workflows/quarkus.yml index 5a92174e957c..289551edb574 100644 --- a/.github/workflows/quarkus.yml +++ b/.github/workflows/quarkus.yml @@ -1,3 +1,43 @@ +# +# Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# name: Nightly Quarkus Tests on: @@ -9,19 +49,15 @@ on: - '.github/workflows/quarkus.yml' schedule: - cron: '0 3 * * *' + workflow_dispatch: env: COMMON_MAVEN_ARGS: "-e -B --settings .github/mvn-settings.xml --fail-at-end" DB_NAME: hibernate_orm_test DB_PASSWORD: hibernate_orm_test DB_USER: hibernate_orm_test - GRAALVM_HOME: ${{ github.workspace }}/graalvm - LABSJDK_HOME: ${{ github.workspace }}/jdk - LANG: en_US.UTF-8 # Workaround testsuite locale issue - MX_GIT_CACHE: refcache - MX_PATH: ${{ github.workspace }}/mx - MX_PYTHON: python3.8 NATIVE_TEST_MAVEN_ARGS: "-Dtest-containers -Dstart-containers -Dquarkus.native.native-image-xmx=5g -Dnative -Dnative.surefire.skip -Dformat.skip -Dno-descriptor-tests install -DskipDocs -Dquarkus.native.container-build=false" + QUARKUS_JAVA_VERSION: 17 # Use Java 17 to build Quarkus as that's the lowest supported JDK version currently QUARKUS_PATH: ${{ github.workspace }}/quarkus permissions: {} @@ -32,24 +68,16 @@ jobs: name: Nightly Quarkus and GraalVM build runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') outputs: matrix: ${{ steps.read.outputs.matrix }} steps: - name: Checkout oracle/graal - uses: actions/checkout@v3 + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm with: - fetch-depth: 1 - - name: Checkout graalvm/mx - uses: actions/checkout@v3 - with: - repository: graalvm/mx.git - fetch-depth: 1 - ref: master - path: ${{ env.MX_PATH }} - - name: Set up Python - uses: actions/setup-python@v4 - with: - python-version: '3.8' + java-version: ${{ env.QUARKUS_JAVA_VERSION }} - name: Get latest Quarkus release run: | export QUARKUS_VERSION=main #$(curl https://repo1.maven.org/maven2/io/quarkus/quarkus-bom/maven-metadata.xml | awk -F"[<>]" '/latest/ {print $3}') @@ -57,34 +85,17 @@ jobs: curl --output quarkus.tgz -sL https://api.github.com/repos/quarkusio/quarkus/tarball/$QUARKUS_VERSION mkdir ${QUARKUS_PATH} tar xf quarkus.tgz -C ${QUARKUS_PATH} --strip-components=1 - - uses: actions/cache@v3 + - uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - - uses: actions/cache@v3 - with: - path: ~/.mx - key: ${{ runner.os }}-mx-${{ hashFiles('**/suite.py') }} - restore-keys: | - ${{ runner.os }}-mx- - - name: Fetch LabsJDK - run: | - mkdir jdk-dl - ${MX_PATH}/mx --java-home= fetch-jdk --jdk-id labsjdk-ce-21 --to jdk-dl --alias ${LABSJDK_HOME} - - name: Build graalvm native-image - run: | - export JAVA_HOME=${LABSJDK_HOME} - cd substratevm - ${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" build - mv $(${MX_PATH}/mx --native=native-image,lib:jvmcicompiler --components="Native Image,LibGraal" graalvm-home) ${GRAALVM_HOME} - ${GRAALVM_HOME}/bin/native-image --version - - name: Tar GraalVM + - name: Tar GraalVM JDK shell: bash - run: tar -czvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) - - name: Persist GraalVM build - uses: actions/upload-artifact@v1 + run: tar -czvhf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) + - name: Persist GraalVM JDK build + uses: actions/upload-artifact@v4 with: name: graalvm path: graalvm.tgz @@ -102,7 +113,7 @@ jobs: shell: bash run: tar -czvf maven-repo.tgz -C ~ .m2/repository - name: Persist Maven Repo - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 with: name: maven-repo path: maven-repo.tgz @@ -111,6 +122,8 @@ jobs: name: Native Tests - ${{matrix.category}} needs: build-quarkus-and-graalvm runs-on: ubuntu-latest + env: + GRAALVM_HOME: ${{ github.workspace }}/graalvm # identical to the one in ./.github/actions/build-graalvm # Ignore the following YAML Schema error timeout-minutes: ${{matrix.timeout}} strategy: @@ -118,13 +131,13 @@ jobs: fail-fast: false matrix: ${{ fromJson(needs.build-quarkus-and-graalvm.outputs.matrix) }} steps: - - name: Download GraalVM build + - name: Download GraalVM JDK build if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: graalvm path: . - - name: Extract GraalVM build + - name: Extract GraalVM JDK build if: startsWith(matrix.os-name, 'ubuntu') shell: bash run: tar -xzvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) @@ -141,7 +154,7 @@ jobs: run: ${QUARKUS_PATH}/.github/ci-prerequisites.sh - name: Download Maven Repo if: startsWith(matrix.os-name, 'ubuntu') - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v4 with: name: maven-repo path: . @@ -149,17 +162,15 @@ jobs: if: startsWith(matrix.os-name, 'ubuntu') shell: bash run: tar -xzf maven-repo.tgz -C ~ - - uses: graalvm/setup-graalvm@v1 + - uses: actions/setup-java@v4 with: - version: 'latest' + distribution: 'oracle' java-version: '17' - github-token: ${{ secrets.GITHUB_TOKEN }} - name: Build with Maven if: startsWith(matrix.os-name, 'ubuntu') env: TEST_MODULES: ${{matrix.test-modules}} run: | - export GRAALVM_HOME=${{ github.workspace }}/graalvm cd ${QUARKUS_PATH} ${GRAALVM_HOME}/bin/native-image --version ./mvnw $COMMON_MAVEN_ARGS -f integration-tests -pl "$TEST_MODULES" $NATIVE_TEST_MAVEN_ARGS @@ -168,7 +179,7 @@ jobs: shell: bash run: find . -type d -name '*-reports' -o -wholename '*/build/reports/tests/functionalTest' | tar -czf test-reports.tgz -T - - name: Upload failure Archive (if maven failed) - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v4 if: failure() with: name: test-reports-native-${{matrix.category}} diff --git a/.github/workflows/reachability-metadata.yml b/.github/workflows/reachability-metadata.yml new file mode 100644 index 000000000000..b19d621f5103 --- /dev/null +++ b/.github/workflows/reachability-metadata.yml @@ -0,0 +1,139 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Weekly Reachability Metadata Tests + +on: + push: + paths: + - '.github/workflows/reachability-metadata.yml' + pull_request: + paths: + - '.github/workflows/reachability-metadata.yml' + schedule: + - cron: '0 1 * * 1' + workflow_dispatch: + +env: + REACHABILITY_METADATA_PATH: ${{ github.workspace }}/graalvm-reachability-metadata + MINIMUM_METADATA_JAVA_VERSION: 17 + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-populate-matrix: + name: Build GraalVM and populate matrix + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + outputs: + matrix: ${{ steps.set-matrix.outputs.matrix }} + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + native-images: 'native-image,native-image-configure,lib:native-image-agent' + components: 'Native Image,Native Image Configure Tool' + java-version: ${{ env.MINIMUM_METADATA_JAVA_VERSION }} + - name: Tar GraalVM JDK + shell: bash + run: tar -czvhf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) $(basename ${GRAALVM_HOME}) + - name: Persist GraalVM JDK build + uses: actions/upload-artifact@v4 + with: + name: graalvm + path: graalvm.tgz + - name: Checkout oracle/graalvm-reachability-metadata + uses: actions/checkout@v4 + with: + repository: oracle/graalvm-reachability-metadata + path: ${{ env.REACHABILITY_METADATA_PATH }} + - name: "Populate matrix" + id: set-matrix + run: | + cd ${{ env.REACHABILITY_METADATA_PATH }} + ./gradlew generateMatrixMatchingCoordinates -Pcoordinates=all + + test-all-metadata: + name: ${{ matrix.coordinates }} + runs-on: ubuntu-latest + env: + GRAALVM_HOME: ${{ github.workspace }}/graalvm # identical to the one in ./.github/actions/build-graalvm + timeout-minutes: 20 + needs: build-graalvm-and-populate-matrix + strategy: + fail-fast: false + matrix: + coordinates: ${{fromJson(needs.build-graalvm-and-populate-matrix.outputs.matrix).coordinates}} + steps: + - name: "Checkout oracle/graalvm-reachability-metadata" + uses: actions/checkout@v4 + with: + repository: oracle/graalvm-reachability-metadata + - name: Download GraalVM JDK build + uses: actions/download-artifact@v4 + with: + name: graalvm + path: . + - name: Extract GraalVM JDK build + run: tar -xzvf graalvm.tgz -C $(dirname ${GRAALVM_HOME}) + - name: "Setup JAVA_HOME" + uses: actions/setup-java@v4 + with: + distribution: 'oracle' + java-version: ${{ env.MINIMUM_METADATA_JAVA_VERSION }} + - name: "Pull allowed docker images" + run: | + ./gradlew pullAllowedDockerImages --coordinates=${{ matrix.coordinates }} + - name: "Disable docker" + run: | + sudo apt-get install openbsd-inetd + sudo bash -c "cat ./.github/workflows/discard-port.conf >> /etc/inetd.conf" + sudo systemctl start inetd + sudo mkdir /etc/systemd/system/docker.service.d + sudo bash -c "cat ./.github/workflows/dockerd.service > /etc/systemd/system/docker.service.d/http-proxy.conf" + sudo systemctl daemon-reload + sudo systemctl restart docker + - name: "Run '${{ matrix.coordinates }}' tests" + run: | + ./gradlew test -Pcoordinates=${{ matrix.coordinates }} + \ No newline at end of file diff --git a/.github/workflows/spring.yml b/.github/workflows/spring.yml new file mode 100644 index 000000000000..3d2c0a6efa58 --- /dev/null +++ b/.github/workflows/spring.yml @@ -0,0 +1,81 @@ +# +# Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. +# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +# +# The Universal Permissive License (UPL), Version 1.0 +# +# Subject to the condition set forth below, permission is hereby granted to any +# person obtaining a copy of this software, associated documentation and/or +# data (collectively the "Software"), free of charge and under any and all +# copyright rights in the Software, and any and all patent rights owned or +# freely licensable by each licensor hereunder covering either (i) the +# unmodified Software as contributed to or provided by such licensor, or (ii) +# the Larger Works (as defined below), to deal in both +# +# (a) the Software, and +# +# (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if +# one is included with the Software each a "Larger Work" to which the Software +# is contributed by such licensors), +# +# without restriction, including without limitation the rights to copy, create +# derivative works of, display, perform, and distribute the Software and make, +# use, sell, offer for sale, import, export, have made, and have sold the +# Software and the Larger Work(s), and to sublicense the foregoing rights on +# either these or other terms. +# +# This license is subject to the following condition: +# +# The above copyright notice and either this complete permission notice or at a +# minimum a reference to the UPL must be included in all copies or substantial +# portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. +# +name: Nightly Spring Tests + +on: + push: + paths: + - '.github/workflows/spring.yml' + pull_request: + paths: + - '.github/workflows/spring.yml' + schedule: + - cron: '0 4 * * *' + workflow_dispatch: + +env: + SPRING_PETCLINIC_PATH: ${{ github.workspace }}/spring-petclinic + SPRING_JAVA_VERSION: 21 + +permissions: + contents: read # to fetch code (actions/checkout) + +jobs: + build-graalvm-and-spring: + name: Native Tests + runs-on: ubuntu-20.04 + if: (github.event_name == 'schedule' && github.repository == 'oracle/graal') || (github.event_name != 'schedule') + steps: + - name: Checkout oracle/graal + uses: actions/checkout@v4 + - name: Build GraalVM JDK + uses: ./.github/actions/build-graalvm + with: + java-version: ${{ env.SPRING_JAVA_VERSION }} + - name: Checkout spring-projects/spring-petclinic + uses: actions/checkout@v4 + with: + repository: spring-projects/spring-petclinic + path: ${{ env.SPRING_PETCLINIC_PATH }} + - name: Run nativeTest in spring-petclinic + run: | + cd ${{ env.SPRING_PETCLINIC_PATH }} + ./gradlew nativeTest diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index 38c5440b0feb..6c70847ea12a 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -229,8 +229,8 @@ class TypeMethodsWithFlagsTest { final Map methodsThatMustExist = new HashMap<>(); final Map methodsThatMustNotExist = new HashMap<>(); - final TypeConfiguration previousConfig = new TypeConfiguration(); - final TypeConfiguration currentConfig = new TypeConfiguration(); + final TypeConfiguration previousConfig = new TypeConfiguration(""); + final TypeConfiguration currentConfig = new TypeConfiguration(""); TypeMethodsWithFlagsTest(ConfigurationMemberDeclaration methodKind) { this.methodKind = methodKind; diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java index 600b21440bb4..edb48f64a3b6 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/ResourceConfigurationTest.java @@ -38,9 +38,9 @@ import org.junit.Test; import com.oracle.svm.configure.config.ResourceConfiguration; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; +import com.oracle.svm.core.util.json.JsonWriter; public class ResourceConfigurationTest { @@ -117,7 +117,7 @@ public void addClassBasedResourceBundle(ConfigurationCondition condition, String } }; - ResourceConfigurationParser rcp = new ResourceConfigurationParser(registry, true); + ResourceConfigurationParser rcp = ResourceConfigurationParser.create(false, registry, true); writerThread.start(); rcp.parseAndRegister(pr); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java index 26a7c7a596da..25962c529838 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationBase.java @@ -26,10 +26,11 @@ import java.util.function.Consumer; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.configure.ConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; + public abstract class ConfigurationBase, P> implements JsonPrintable { public abstract boolean isEmpty(); @@ -68,5 +69,5 @@ public T copyAndFilter(P predicate) { return copyAnd(copy -> copy.removeIf(predicate)); } - public abstract ConfigurationParser createParser(); + public abstract ConfigurationParser createParser(boolean strictMetadata); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java index fbc031bc8402..cf2fc9046ad6 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -24,10 +24,14 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.io.IOException; import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashSet; @@ -35,13 +39,16 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.VMError; public class ConfigurationFileCollection { public static final Function FAIL_ON_EXCEPTION = e -> e; + private final Set reachabilityMetadataPaths = new LinkedHashSet<>(); private final Set jniConfigPaths = new LinkedHashSet<>(); private final Set reflectConfigPaths = new LinkedHashSet<>(); private final Set proxyConfigPaths = new LinkedHashSet<>(); @@ -51,6 +58,7 @@ public class ConfigurationFileCollection { private Set lockFilePaths; public void addDirectory(Path path) { + reachabilityMetadataPaths.add(path.resolve(ConfigurationFile.REACHABILITY_METADATA.getFileName()).toUri()); jniConfigPaths.add(path.resolve(ConfigurationFile.JNI.getFileName()).toUri()); reflectConfigPaths.add(path.resolve(ConfigurationFile.REFLECTION.getFileName()).toUri()); proxyConfigPaths.add(path.resolve(ConfigurationFile.DYNAMIC_PROXY.getFileName()).toUri()); @@ -70,24 +78,51 @@ private void detectAgentLock(T location, Predicate exists, Function fileResolver) { - jniConfigPaths.add(fileResolver.apply(ConfigurationFile.JNI.getFileName())); - reflectConfigPaths.add(fileResolver.apply(ConfigurationFile.REFLECTION.getFileName())); - proxyConfigPaths.add(fileResolver.apply(ConfigurationFile.DYNAMIC_PROXY.getFileName())); - resourceConfigPaths.add(fileResolver.apply(ConfigurationFile.RESOURCES.getFileName())); - serializationConfigPaths.add(fileResolver.apply(ConfigurationFile.SERIALIZATION.getFileName())); - predefinedClassesConfigPaths.add(fileResolver.apply(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName())); + addFile(reachabilityMetadataPaths, fileResolver, ConfigurationFile.REACHABILITY_METADATA); + addFile(jniConfigPaths, fileResolver, ConfigurationFile.JNI); + addFile(reflectConfigPaths, fileResolver, ConfigurationFile.REFLECTION); + addFile(proxyConfigPaths, fileResolver, ConfigurationFile.DYNAMIC_PROXY); + addFile(resourceConfigPaths, fileResolver, ConfigurationFile.RESOURCES); + addFile(serializationConfigPaths, fileResolver, ConfigurationFile.SERIALIZATION); + addFile(predefinedClassesConfigPaths, fileResolver, ConfigurationFile.PREDEFINED_CLASSES_NAME); detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity()); } + private static void addFile(Set metadataPaths, Function fileResolver, ConfigurationFile configurationFile) { + URI uri = fileResolver.apply(configurationFile.getFileName()); + if (uri != null) { + metadataPaths.add(uri); + } + } + public Set getDetectedAgentLockPaths() { return (lockFilePaths != null) ? lockFilePaths : Collections.emptySet(); } public boolean isEmpty() { - return jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && + return reachabilityMetadataPaths.isEmpty() && jniConfigPaths.isEmpty() && reflectConfigPaths.isEmpty() && proxyConfigPaths.isEmpty() && resourceConfigPaths.isEmpty() && serializationConfigPaths.isEmpty() && predefinedClassesConfigPaths.isEmpty(); } + public Set getPaths(ConfigurationFile configurationFile) { + Set uris; + switch (configurationFile) { + case REACHABILITY_METADATA -> uris = getReachabilityMetadataPaths(); + case DYNAMIC_PROXY -> uris = getProxyConfigPaths(); + case RESOURCES -> uris = getResourceConfigPaths(); + case JNI -> uris = getJniConfigPaths(); + case REFLECTION -> uris = getReflectConfigPaths(); + case SERIALIZATION -> uris = getSerializationConfigPaths(); + case PREDEFINED_CLASSES_NAME -> uris = getPredefinedClassesConfigPaths(); + default -> throw VMError.shouldNotReachHere("Cannot get paths for configuration file " + configurationFile); + } + return uris.stream().map(Paths::get).collect(Collectors.toSet()); + } + + public Set getReachabilityMetadataPaths() { + return reachabilityMetadataPaths; + } + public Set getJniConfigPaths() { return jniConfigPaths; } @@ -113,35 +148,37 @@ public Set getPredefinedClassesConfigPaths() { } public TypeConfiguration loadJniConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(jniConfigPaths, exceptionHandler); + return loadTypeConfig(JNI_KEY, jniConfigPaths, exceptionHandler); } public TypeConfiguration loadReflectConfig(Function exceptionHandler) throws Exception { - return loadTypeConfig(reflectConfigPaths, exceptionHandler); + return loadTypeConfig(REFLECTION_KEY, reflectConfigPaths, exceptionHandler); } public ProxyConfiguration loadProxyConfig(Function exceptionHandler) throws Exception { ProxyConfiguration proxyConfiguration = new ProxyConfiguration(); - loadConfig(proxyConfigPaths, proxyConfiguration.createParser(), exceptionHandler); + loadConfig(proxyConfigPaths, proxyConfiguration.createParser(false), exceptionHandler); return proxyConfiguration; } public PredefinedClassesConfiguration loadPredefinedClassesConfig(Path[] classDestinationDirs, Predicate shouldExcludeClassesWithHash, Function exceptionHandler) throws Exception { PredefinedClassesConfiguration predefinedClassesConfiguration = new PredefinedClassesConfiguration(classDestinationDirs, shouldExcludeClassesWithHash); - loadConfig(predefinedClassesConfigPaths, predefinedClassesConfiguration.createParser(), exceptionHandler); + loadConfig(predefinedClassesConfigPaths, predefinedClassesConfiguration.createParser(false), exceptionHandler); return predefinedClassesConfiguration; } public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); - loadConfig(resourceConfigPaths, resourceConfiguration.createParser(), exceptionHandler); + loadConfig(reachabilityMetadataPaths, resourceConfiguration.createParser(true), exceptionHandler); + loadConfig(resourceConfigPaths, resourceConfiguration.createParser(false), exceptionHandler); return resourceConfiguration; } public SerializationConfiguration loadSerializationConfig(Function exceptionHandler) throws Exception { SerializationConfiguration serializationConfiguration = new SerializationConfiguration(); - loadConfig(serializationConfigPaths, serializationConfiguration.createParser(), exceptionHandler); + loadConfig(reachabilityMetadataPaths, serializationConfiguration.createParser(true), exceptionHandler); + loadConfig(serializationConfigPaths, serializationConfiguration.createParser(false), exceptionHandler); return serializationConfiguration; } @@ -152,9 +189,10 @@ public ConfigurationSet loadConfigurationSet(Function ex loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler)); } - private static TypeConfiguration loadTypeConfig(Collection uris, Function exceptionHandler) throws Exception { - TypeConfiguration configuration = new TypeConfiguration(); - loadConfig(uris, configuration.createParser(), exceptionHandler); + private TypeConfiguration loadTypeConfig(String combinedFileKey, Collection uris, Function exceptionHandler) throws Exception { + TypeConfiguration configuration = new TypeConfiguration(combinedFileKey); + loadConfig(reachabilityMetadataPaths, configuration.createParser(true), exceptionHandler); + loadConfig(uris, configuration.createParser(false), exceptionHandler); return configuration; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index 166eb5179747..4397508d5fa2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -24,6 +24,9 @@ */ package com.oracle.svm.configure.config; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; @@ -32,10 +35,10 @@ import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.config.conditional.ConditionalConfigurationPredicate; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public class ConfigurationSet { @FunctionalInterface @@ -66,7 +69,7 @@ public ConfigurationSet(ConfigurationSet other) { } public ConfigurationSet() { - this(new TypeConfiguration(), new TypeConfiguration(), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), + this(new TypeConfiguration(REFLECTION_KEY), new TypeConfiguration(JNI_KEY), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), new PredefinedClassesConfiguration(new Path[0], hash -> false)); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java index 62a199c3a15e..095ea107b671 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ParserConfigurationAdapter.java @@ -31,6 +31,8 @@ import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberAccessibility; import com.oracle.svm.configure.config.ConfigurationMemberInfo.ConfigurationMemberDeclaration; import com.oracle.svm.core.TypeResult; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; public class ParserConfigurationAdapter implements ReflectionConfigurationParserDelegate { @@ -42,114 +44,114 @@ public ParserConfigurationAdapter(TypeConfiguration configuration) { } @Override - public TypeResult resolveCondition(String typeName) { - return TypeResult.forType(typeName, ConfigurationCondition.create(typeName)); + public TypeResult resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + if (typeDescriptor instanceof NamedConfigurationTypeDescriptor namedDescriptor) { + String typeName = namedDescriptor.name(); + ConfigurationType type = configuration.get(condition, typeName); + ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); + return TypeResult.forType(typeName, result); + } else { + return TypeResult.forException(typeDescriptor.toString(), null); + } } @Override - public TypeResult resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - ConfigurationType type = configuration.get(condition, typeName); - ConfigurationType result = type != null ? type : new ConfigurationType(condition, typeName); - return TypeResult.forType(typeName, result); - } - - @Override - public void registerType(ConfigurationType type) { + public void registerType(ConfigurationCondition condition, ConfigurationType type) { configuration.add(type); } @Override - public void registerField(ConfigurationType type, String fieldName, boolean finalButWritable) { + public void registerField(ConfigurationCondition condition, ConfigurationType type, String fieldName, boolean finalButWritable) { type.addField(fieldName, ConfigurationMemberDeclaration.PRESENT, finalButWritable); } @Override - public boolean registerAllMethodsWithName(boolean queriedOnly, ConfigurationType type, String methodName) { + public boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, String methodName) { type.addMethodsWithName(methodName, ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public boolean registerAllConstructors(boolean queriedOnly, ConfigurationType type) { + public boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.addMethodsWithName(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); return true; } @Override - public void registerUnsafeAllocated(ConfigurationType type) { + public void registerUnsafeAllocated(ConfigurationCondition condition, ConfigurationType type) { type.setUnsafeAllocated(); } @Override - public void registerMethod(boolean queriedOnly, ConfigurationType type, String methodName, List methodParameterTypes) { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, String methodName, List methodParameterTypes) { type.addMethod(methodName, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerConstructor(boolean queriedOnly, ConfigurationType type, List methodParameterTypes) { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type, List methodParameterTypes) { type.addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, ConfigurationMethod.toInternalParamsSignature(methodParameterTypes), ConfigurationMemberDeclaration.PRESENT, queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerPublicClasses(ConfigurationType type) { + public void registerPublicClasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllPublicClasses(); } @Override - public void registerDeclaredClasses(ConfigurationType type) { + public void registerDeclaredClasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllDeclaredClasses(); } @Override - public void registerRecordComponents(ConfigurationType type) { + public void registerRecordComponents(ConfigurationCondition condition, ConfigurationType type) { type.setAllRecordComponents(); } @Override - public void registerPermittedSubclasses(ConfigurationType type) { + public void registerPermittedSubclasses(ConfigurationCondition condition, ConfigurationType type) { type.setAllPermittedSubclasses(); } @Override - public void registerNestMembers(ConfigurationType type) { + public void registerNestMembers(ConfigurationCondition condition, ConfigurationType type) { type.setAllNestMembers(); } @Override - public void registerSigners(ConfigurationType type) { + public void registerSigners(ConfigurationCondition condition, ConfigurationType type) { type.setAllSigners(); } @Override - public void registerPublicFields(ConfigurationType type) { + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicFields(); } @Override - public void registerDeclaredFields(ConfigurationType type) { + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredFields(); } @Override - public void registerPublicMethods(boolean queriedOnly, ConfigurationType type) { + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConfigurationType type) { + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredMethods(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConfigurationType type) { + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllPublicConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConfigurationType type) { + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, ConfigurationType type) { type.setAllDeclaredConstructors(queriedOnly ? ConfigurationMemberAccessibility.QUERIED : ConfigurationMemberAccessibility.ACCESSED); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java index 40229ed4648f..87e86fa0309f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/PredefinedClassesConfiguration.java @@ -34,14 +34,15 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.oracle.svm.core.configure.ConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; +import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.PredefinedClassesConfigurationParser; import com.oracle.svm.core.hub.PredefinedClassesSupport; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class PredefinedClassesConfiguration extends ConfigurationBase { private final Path[] classDestinationDirs; @@ -164,7 +165,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { + public ConfigurationParser createParser(boolean strictMetadata) { + VMError.guarantee(!strictMetadata, "Predefined classes configuration is not supported with strict metadata"); return new PredefinedClassesConfigurationParser(this::add, true); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java index 9859a20d9e55..98715fbba05c 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ProxyConfiguration.java @@ -31,13 +31,14 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.configure.ProxyConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ProxyConfigurationParser; +import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class ProxyConfiguration extends ConfigurationBase { private final Set>> interfaceLists = ConcurrentHashMap.newKeySet(); @@ -129,8 +130,9 @@ public static void printProxyInterfaces(JsonWriter writer, List interfaceLists.add(new ConditionalElement<>(cond, intfs))); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java index 803fbb86fa34..b990bdca9b43 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ResourceConfiguration.java @@ -38,13 +38,13 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonPrinter; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ResourceConfigurationParser; import com.oracle.svm.core.configure.ResourcesRegistry; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; public final class ResourceConfiguration extends ConfigurationBase { @@ -249,8 +249,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new ResourceConfigurationParser(new ResourceConfiguration.ParserAdapter(this), true); + public ConfigurationParser createParser(boolean strictMetadata) { + return ResourceConfigurationParser.create(strictMetadata, new ResourceConfiguration.ParserAdapter(this), true); } private static void printResourceBundle(BundleConfiguration config, JsonWriter writer) throws IOException { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java index 1ed2ed4d756b..c59d78976a54 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/SerializationConfiguration.java @@ -37,11 +37,11 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.SerializationConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public final class SerializationConfiguration extends ConfigurationBase implements RuntimeSerializationSupport { @@ -123,8 +123,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new SerializationConfigurationParser(this, true); + public ConfigurationParser createParser(boolean strictMetadata) { + return SerializationConfigurationParser.create(strictMetadata, this, true); } private void printProxies(JsonWriter writer) throws IOException { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index b90efed5dfdb..32d02920d90d 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java @@ -32,24 +32,28 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; -import com.oracle.svm.core.configure.ConfigurationParser; -import com.oracle.svm.core.configure.ReflectionConfigurationParser; import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.configure.ReflectionConfigurationParser; import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.util.json.JsonWriter; public final class TypeConfiguration extends ConfigurationBase { private final ConcurrentMap, ConfigurationType> types = new ConcurrentHashMap<>(); - public TypeConfiguration() { + private final String combinedFileKey; + + public TypeConfiguration(String combinedFileKey) { + this.combinedFileKey = combinedFileKey; } public TypeConfiguration(TypeConfiguration other) { other.types.forEach((key, value) -> types.put(key, new ConfigurationType(value))); + this.combinedFileKey = other.combinedFileKey; } @Override @@ -142,8 +146,8 @@ public void printJson(JsonWriter writer) throws IOException { } @Override - public ConfigurationParser createParser() { - return new ReflectionConfigurationParser<>(new ParserConfigurationAdapter(this), true, false); + public ConfigurationParser createParser(boolean strictMetadata) { + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, new ParserConfigurationAdapter(this), true, false); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java index e7ee09dd8ccb..cb56f8c7c52d 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/PartialConfigurationWithOrigins.java @@ -33,10 +33,10 @@ import org.graalvm.util.json.JSONParserException; import com.oracle.svm.configure.config.ConfigurationSet; -import com.oracle.svm.core.util.json.JsonPrintable; -import com.oracle.svm.core.util.json.JsonWriter; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationParser; +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.core.util.json.JsonWriter; public class PartialConfigurationWithOrigins extends ConfigurationParser implements JsonPrintable { private static final ConfigurationSet emptyConfigurationSet = new ConfigurationSet(); @@ -169,7 +169,7 @@ private static void parseConfigurationSet(EconomicMap configJson, Con if (configType == null) { throw new JSONParserException("Invalid configuration type: " + configName); } - configurationSet.getConfiguration(configType).createParser().parseAndRegister(cursor.getValue(), origin); + configurationSet.getConfiguration(configType).createParser(false).parseAndRegister(cursor.getValue(), origin); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java new file mode 100644 index 000000000000..f63e5947d040 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationConditionResolver.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +public interface ConfigurationConditionResolver { + + static ConfigurationConditionResolver identityResolver() { + return new ConfigurationConditionResolver() { + @Override + public TypeResult resolveCondition(ConfigurationCondition unresolvedCondition) { + return TypeResult.forType(unresolvedCondition.getTypeName(), unresolvedCondition); + } + + @Override + public ConfigurationCondition alwaysTrue() { + return ConfigurationCondition.alwaysTrue(); + } + }; + } + + TypeResult resolveCondition(ConfigurationCondition unresolvedCondition); + + ConfigurationCondition alwaysTrue(); +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java index 98dac61e3b0c..8ec71e14ba5a 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFile.java @@ -24,21 +24,34 @@ */ package com.oracle.svm.core.configure; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.RESOURCES_KEY; +import static com.oracle.svm.core.configure.ConfigurationParser.SERIALIZATION_KEY; + import java.util.Arrays; public enum ConfigurationFile { - DYNAMIC_PROXY("proxy", true), - RESOURCES("resource", true), - JNI("jni", true), - FOREIGN("foreign", false), - REFLECTION("reflect", true), - SERIALIZATION("serialization", true), - SERIALIZATION_DENY("serialization-deny", false), - PREDEFINED_CLASSES_NAME("predefined-classes", true); + /* Combined file */ + REACHABILITY_METADATA("reachability-metadata", null, true, true), + /* Main metadata categories (order matters) */ + REFLECTION("reflect", REFLECTION_KEY, true, false), + RESOURCES("resource", RESOURCES_KEY, true, false), + SERIALIZATION("serialization", SERIALIZATION_KEY, true, false), + JNI("jni", JNI_KEY, true, false), + /* Deprecated metadata categories */ + DYNAMIC_PROXY("proxy", null, true, false), + PREDEFINED_CLASSES_NAME("predefined-classes", null, true, false), + /* Non-metadata categories */ + FOREIGN("foreign", null, false, false), + SERIALIZATION_DENY("serialization-deny", null, false, false); - public static final String DEFAULT_FILE_NAME_SUFFIX = "-config.json"; + public static final String LEGACY_FILE_NAME_SUFFIX = "-config.json"; + public static final String COMBINED_FILE_NAME_SUFFIX = ".json"; private final String name; + private final String fieldName; private final boolean canAgentGenerate; + private final boolean combinedFile; public static final String LOCK_FILE_NAME = ".lock"; public static final String PREDEFINED_CLASSES_AGENT_EXTRACTED_SUBDIR = "agent-extracted-predefined-classes"; @@ -47,17 +60,23 @@ public enum ConfigurationFile { private static final ConfigurationFile[] agentGeneratedFiles = computeAgentGeneratedFiles(); - ConfigurationFile(String name, boolean canAgentGenerate) { + ConfigurationFile(String name, String fieldName, boolean canAgentGenerate, boolean combinedFile) { this.name = name; + this.fieldName = fieldName; this.canAgentGenerate = canAgentGenerate; + this.combinedFile = combinedFile; } public String getName() { return name; } + public String getFieldName() { + return fieldName; + } + public String getFileName() { - return name + DEFAULT_FILE_NAME_SUFFIX; + return name + (combinedFile ? COMBINED_FILE_NAME_SUFFIX : LEGACY_FILE_NAME_SUFFIX); } public String getFileName(String suffix) { @@ -65,7 +84,7 @@ public String getFileName(String suffix) { } public boolean canBeGeneratedByAgent() { - return canAgentGenerate; + return canAgentGenerate && !combinedFile; } public static ConfigurationFile getByName(String name) { @@ -82,6 +101,6 @@ public static ConfigurationFile[] agentGeneratedFiles() { } private static ConfigurationFile[] computeAgentGeneratedFiles() { - return Arrays.stream(values()).filter(ConfigurationFile::canBeGeneratedByAgent).toArray(ConfigurationFile[]::new); + return Arrays.stream(values()).filter(f -> f.canBeGeneratedByAgent()).toArray(ConfigurationFile[]::new); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java index c78a2ea1d5ee..d32581732677 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationFiles.java @@ -96,6 +96,11 @@ public static final class Options { @Option(help = "Resources describing program elements to be made accessible via JNI (see JNIConfigurationFiles).", type = OptionType.User)// public static final HostedOptionKey JNIConfigurationResources = new HostedOptionKey<>(LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Resources describing reachability metadata needed for the program " + + "https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reachability-metadata-schema-v1.0.0.json", type = OptionType.User)// + public static final HostedOptionKey ReachabilityMetadataResources = new HostedOptionKey<>( + LocatableMultiOptionValue.Strings.buildWithCommaDelimiter()); + @Option(help = "Files describing stubs allowing foreign calls.", type = OptionType.User)// @BundleMember(role = BundleMember.Role.Input)// public static final HostedOptionKey ForeignConfigurationFiles = new HostedOptionKey<>(LocatableMultiOptionValue.Paths.buildWithCommaDelimiter()); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java index 49b77297caf5..703cb39745fe 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package com.oracle.svm.core.configure; import java.io.BufferedReader; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,6 +38,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import org.graalvm.collections.EconomicMap; @@ -61,6 +63,18 @@ public static InputStream openStream(URI uri) throws IOException { public static final String CONDITIONAL_KEY = "condition"; public static final String TYPE_REACHABLE_KEY = "typeReachable"; + public static final String TYPE_REACHED_KEY = "typeReached"; + public static final String NAME_KEY = "name"; + public static final String TYPE_KEY = "type"; + public static final String PROXY_KEY = "proxy"; + public static final String REFLECTION_KEY = "reflection"; + public static final String JNI_KEY = "jni"; + public static final String SERIALIZATION_KEY = "serialization"; + public static final String RESOURCES_KEY = "resources"; + public static final String BUNDLES_KEY = "bundles"; + public static final String GLOBS_KEY = "globs"; + public static final String GLOB_KEY = "glob"; + public static final String MODULE_KEY = "module"; private final Map> seenUnknownAttributesByType = new HashMap<>(); private final boolean strictSchema; @@ -71,6 +85,11 @@ protected ConfigurationParser(boolean strictConfiguration) { public void parseAndRegister(URI uri) throws IOException { try (Reader reader = openReader(uri)) { parseAndRegister(new JSONParser(reader).parse(), uri); + } catch (FileNotFoundException e) { + /* + * Ignore: *-config.json files can be missing when reachability-metadata.json is + * present, and vice-versa + */ } } @@ -84,6 +103,12 @@ public void parseAndRegister(Reader reader) throws IOException { public abstract void parseAndRegister(Object json, URI origin) throws IOException; + public Object getFromGlobalFile(Object json, String key) { + EconomicMap map = asMap(json, "top level of reachability metadata file must be an object"); + checkAttributes(map, "reachability metadata", Collections.emptyList(), List.of(REFLECTION_KEY, JNI_KEY, SERIALIZATION_KEY, RESOURCES_KEY, BUNDLES_KEY, "reason", "comment")); + return map.get(key); + } + @SuppressWarnings("unchecked") public static List asList(Object data, String errorMessage) { if (data instanceof List) { @@ -127,6 +152,23 @@ protected void checkAttributes(EconomicMap map, String type, Col } } + public static void checkHasExactlyOneAttribute(EconomicMap map, String type, Collection alternativeAttributes) { + boolean attributeFound = false; + for (String key : map.getKeys()) { + if (alternativeAttributes.contains(key)) { + if (attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + attributeFound = true; + } + } + if (!attributeFound) { + String message = "Exactly one of [" + String.join(", ", alternativeAttributes) + "] must be set in " + type; + throw new JSONParserException(message); + } + } + /** * Used to warn about schema errors in configuration files. Should never be used if the type is * missing. @@ -135,7 +177,7 @@ protected void checkAttributes(EconomicMap map, String type, Col */ protected void warnOrFailOnSchemaError(String message) { if (strictSchema) { - throw new JSONParserException(message); + failOnSchemaError(message); } else { LogUtils.warning(message); } @@ -180,18 +222,89 @@ protected static long asLong(Object value, String propertyName) { throw new JSONParserException("Invalid long value '" + value + "' for element '" + propertyName + "'"); } - protected ConfigurationCondition parseCondition(EconomicMap data) { + private static boolean alreadyWarned = false; + + protected ConfigurationCondition parseCondition(EconomicMap data, boolean runtimeCondition) { Object conditionData = data.get(CONDITIONAL_KEY); if (conditionData != null) { EconomicMap conditionObject = asMap(conditionData, "Attribute 'condition' must be an object"); - Object conditionType = conditionObject.get(TYPE_REACHABLE_KEY); - if (conditionType instanceof String) { - return ConfigurationCondition.create((String) conditionType); - } else { - warnOrFailOnSchemaError("'" + TYPE_REACHABLE_KEY + "' should be of type string"); + if (conditionObject.containsKey(TYPE_REACHABLE_KEY) && conditionObject.containsKey(TYPE_REACHED_KEY)) { + failOnSchemaError("condition can not have both '" + TYPE_REACHED_KEY + "' and '" + TYPE_REACHABLE_KEY + "' set."); + } else if (conditionObject.isEmpty()) { + failOnSchemaError("condition can not be empty"); + } + + if (conditionObject.containsKey(TYPE_REACHED_KEY)) { + if (!runtimeCondition) { + failOnSchemaError("'" + TYPE_REACHED_KEY + "' condition cannot be used in older schemas. Please migrate the file to the latest schema."); + } + Object object = conditionObject.get(TYPE_REACHED_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + if (!alreadyWarned) { + LogUtils.warning("Found typeReached condition in JSON configuration files. " + + "The typeReached condition is not supported by this version of GraalVM and will be considered as always true. " + + "Please consider upgrading to the latest GraalVM version to be able to use all the latest features."); + alreadyWarned = true; + } + return ConfigurationCondition.alwaysTrue(); + } + } else if (conditionObject.containsKey(TYPE_REACHABLE_KEY)) { + if (runtimeCondition) { + failOnSchemaError("'" + TYPE_REACHABLE_KEY + "' condition can not be used with the latest schema. Please use '" + TYPE_REACHED_KEY + "'."); + } + Object object = conditionObject.get(TYPE_REACHABLE_KEY); + var condition = parseTypeContents(object); + if (condition.isPresent()) { + String className = ((NamedConfigurationTypeDescriptor) condition.get()).name(); + return ConfigurationCondition.create(className); + } } } + /* + * Ensure forward-compatibility with condition types added in the future + */ return ConfigurationCondition.alwaysTrue(); } + private static JSONParserException failOnSchemaError(String message) { + throw new JSONParserException(message); + } + + protected record TypeDescriptorWithOrigin(ConfigurationTypeDescriptor typeDescriptor, boolean definedAsType) { + } + + protected static Optional parseName(EconomicMap data, boolean treatAllNameEntriesAsType) { + Object name = data.get(NAME_KEY); + if (name != null) { + NamedConfigurationTypeDescriptor typeDescriptor = new NamedConfigurationTypeDescriptor(asString(name)); + return Optional.of(new TypeDescriptorWithOrigin(typeDescriptor, treatAllNameEntriesAsType)); + } else { + throw failOnSchemaError("must have type or name specified for an element"); + } + } + + protected static Optional parseTypeContents(Object typeObject) { + if (typeObject instanceof String stringValue) { + return Optional.of(new NamedConfigurationTypeDescriptor(stringValue)); + } else { + EconomicMap type = asMap(typeObject, "type descriptor should be a string or object"); + if (type.containsKey(PROXY_KEY)) { + checkHasExactlyOneAttribute(type, "type descriptor object", Set.of(PROXY_KEY)); + return Optional.of(getProxyDescriptor(type.get(PROXY_KEY))); + } + /* + * We return if we find a future version of a type descriptor (as a JSON object) instead + * of failing parsing. + */ + // TODO warn + return Optional.empty(); + } + } + + private static ProxyConfigurationTypeDescriptor getProxyDescriptor(Object proxyObject) { + List proxyInterfaces = asList(proxyObject, "proxy interface content should be an interface list"); + List proxyInterfaceNames = proxyInterfaces.stream().map(obj -> asString(obj, "proxy")).toList(); + return new ProxyConfigurationTypeDescriptor(proxyInterfaceNames); + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..49e04564f2d4 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ConfigurationTypeDescriptor.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.util.Collection; + +import org.graalvm.nativeimage.ImageInfo; + +import com.oracle.svm.core.util.json.JsonPrintable; +import com.oracle.svm.util.LogUtils; + +import jdk.vm.ci.meta.MetaUtil; + +/** + * Provides a representation of a Java type based on String type names. This is used to parse types + * in configuration files. The supported types are: + * + *
    + *
  • Named types: regular Java types described by their fully qualified name.
  • + *
+ */ +public interface ConfigurationTypeDescriptor extends Comparable, JsonPrintable { + static String canonicalizeTypeName(String typeName) { + if (typeName == null) { + return null; + } + String name = typeName; + if (name.indexOf('[') != -1) { + /* accept "int[][]", "java.lang.String[]" */ + name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + } + return name; + } + + enum Kind { + NAMED, + PROXY + } + + Kind getDescriptorType(); + + @Override + String toString(); + + /** + * Returns the qualified names of all named Java types (excluding proxy classes, lambda classes + * and similar anonymous classes) required for this type descriptor to properly describe its + * type. This is used to filter configurations based on a String-based class filter. + */ + Collection getAllQualifiedJavaNames(); + + static String checkQualifiedJavaName(String javaName) { + if (ImageInfo.inImageBuildtimeCode() && !(javaName.indexOf('/') == -1 || javaName.indexOf('/') > javaName.lastIndexOf('.'))) { + LogUtils.warning("Type descriptor requires qualified Java name, not internal representation: %s", javaName); + } + return canonicalizeTypeName(javaName); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java new file mode 100644 index 000000000000..064e99e9017e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyReflectionConfigurationParser.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +final class LegacyReflectionConfigurationParser extends ReflectionConfigurationParser { + + private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", + "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", + "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", + "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, + "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); + + private final boolean treatAllNameEntriesAsType; + + LegacyReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements, boolean treatAllNameEntriesAsType) { + super(conditionResolver, delegate, strictConfiguration, printMissingElements); + this.treatAllNameEntriesAsType = treatAllNameEntriesAsType; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + parseClassArray(asList(json, "first level of document must be an array of class descriptors")); + } + + @Override + protected void parseClass(EconomicMap data) { + checkAttributes(data, "reflection class descriptor object", List.of(NAME_KEY), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); + + Optional type = parseName(data, treatAllNameEntriesAsType); + if (type.isEmpty()) { + return; + } + ConfigurationTypeDescriptor typeDescriptor = type.get().typeDescriptor(); + /* + * Classes registered using the old ("name") syntax requires elements (fields, methods, + * constructors, ...) to be registered for runtime queries, whereas the new ("type") syntax + * automatically registers all elements as queried. + */ + boolean isType = type.get().definedAsType(); + + ConfigurationCondition unresolvedCondition = parseCondition(data, isType); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + ConfigurationCondition condition = conditionResult.get(); + TypeResult result = delegate.resolveType(condition, typeDescriptor, true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + typeDescriptor + " for reflection configuration.", result.getException()); + return; + } + + ConfigurationCondition queryCondition = isType ? conditionResolver.alwaysTrue() : condition; + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, isType, clazz, "allDeclaredClasses", () -> delegate.registerDeclaredClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allRecordComponents", () -> delegate.registerRecordComponents(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPermittedSubclasses", () -> delegate.registerPermittedSubclasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allNestMembers", () -> delegate.registerNestMembers(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allSigners", () -> delegate.registerSigners(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "allPublicClasses", () -> delegate.registerPublicClasses(queryCondition, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredConstructors", () -> delegate.registerDeclaredConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicConstructors", () -> delegate.registerPublicConstructors(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllDeclaredMethods", () -> delegate.registerDeclaredMethods(queryCondition, true, clazz)); + registerIfNotDefault(data, isType, clazz, "queryAllPublicMethods", () -> delegate.registerPublicMethods(queryCondition, true, clazz)); + if (isType) { + /* + * Fields cannot be registered as queried only by the user, we register them + * unconditionally if the class is registered via "type". + */ + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + } + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "queriedMethods": + parseMethods(condition, true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java new file mode 100644 index 000000000000..525b43cc1d49 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacyResourceConfigurationParser.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +final class LegacyResourceConfigurationParser extends ResourceConfigurationParser { + LegacyResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + parseTopLevelObject(asMap(json, "first level of document must be an object")); + } + + @Override + protected ConfigurationCondition parseCondition(EconomicMap data) { + return parseCondition(data, false); + } + + private void parseTopLevelObject(EconomicMap obj) { + Object resourcesObject = null; + Object bundlesObject = null; + Object globsObject = null; + MapCursor cursor = obj.getEntries(); + while (cursor.advance()) { + if (RESOURCES_KEY.equals(cursor.getKey())) { + resourcesObject = cursor.getValue(); + } else if (BUNDLES_KEY.equals(cursor.getKey())) { + bundlesObject = cursor.getValue(); + } else if (GLOBS_KEY.equals(cursor.getKey())) { + globsObject = cursor.getValue(); + } + } + + if (resourcesObject != null) { + parseResourcesObject(resourcesObject); + } + if (bundlesObject != null) { + parseBundlesObject(bundlesObject); + } + if (globsObject != null) { + parseGlobsObject(globsObject); + } + } + + @SuppressWarnings("unchecked") + private void parseResourcesObject(Object resourcesObject) { + if (resourcesObject instanceof EconomicMap) { // New format + EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; + checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); + Object includesObject = resourcesObjectMap.get("includes"); + Object excludesObject = resourcesObjectMap.get("excludes"); + + List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); + for (Object object : includes) { + parsePatternEntry(object, registry::addResources, "'includes' list"); + } + + if (excludesObject != null) { + List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); + for (Object object : excludes) { + parsePatternEntry(object, registry::ignoreResources, "'excludes' list"); + } + } + } else { // Old format: may be deprecated in future versions + List resources = asList(resourcesObject, "Attribute 'resources' must be a list of resources"); + for (Object object : resources) { + parsePatternEntry(object, registry::addResources, "'resources' list"); + } + } + } + + private void parsePatternEntry(Object data, BiConsumer resourceRegistry, String parentType) { + EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a resource descriptor object"); + checkAttributes(resource, "regex resource descriptor object", Collections.singletonList("pattern"), Collections.singletonList(CONDITIONAL_KEY)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource, false)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } + + Object valueObject = resource.get("pattern"); + String value = asString(valueObject, "pattern"); + resourceRegistry.accept(resolvedConfigurationCondition.get(), value); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java new file mode 100644 index 000000000000..b94f83b3812e --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/LegacySerializationConfigurationParser.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; +import org.graalvm.util.json.JSONParserException; + +final class LegacySerializationConfigurationParser extends SerializationConfigurationParser { + + private static final String SERIALIZATION_TYPES_KEY = "types"; + private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; + private static final String PROXY_SERIALIZATION_TYPES_KEY = "proxies"; + + private final ProxyConfigurationParser proxyConfigurationParser; + + LegacySerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + this.proxyConfigurationParser = new ProxyConfigurationParser(strictConfiguration, serializationSupport::registerProxyClass); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + if (json instanceof List) { + parseOldConfiguration(asList(json, "First-level of document must be an array of serialization lists")); + } else if (json instanceof EconomicMap) { + parseNewConfiguration(asMap(json, "First-level of document must be a map of serialization types")); + } else { + throw new JSONParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + } + } + + private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { + parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); + } + + private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { + if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { + throw new JSONParserException("Second-level of document must be arrays of serialization descriptor objects"); + } + + parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); + parseSerializationTypes( + asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), + "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), + true); + + if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { + proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); + } + } + + @Override + protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { + if (lambdaCapturingType) { + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); + } else { + checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); + } + + NamedConfigurationTypeDescriptor targetSerializationClass = new NamedConfigurationTypeDescriptor(asString(data.get(NAME_KEY))); + ConfigurationCondition unresolvedCondition = parseCondition(data, false); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + if (lambdaCapturingType) { + String className = targetSerializationClass.name(); + serializationSupport.registerLambdaCapturingClass(condition.get(), className); + } else { + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + serializationSupport.registerWithTargetConstructorClass(condition.get(), targetSerializationClass.name(), customTargetConstructorClass); + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..86b33152bf47 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/NamedConfigurationTypeDescriptor.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.io.IOException; +import java.util.Collection; +import java.util.Collections; + +import com.oracle.svm.core.util.json.JsonWriter; + +public record NamedConfigurationTypeDescriptor(String name) implements ConfigurationTypeDescriptor { + + public NamedConfigurationTypeDescriptor(String name) { + this.name = ConfigurationTypeDescriptor.checkQualifiedJavaName(name); + } + + @Override + public Kind getDescriptorType() { + return Kind.NAMED; + } + + @Override + public String toString() { + return name; + } + + @Override + public Collection getAllQualifiedJavaNames() { + return Collections.singleton(name); + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof NamedConfigurationTypeDescriptor namedOther) { + return name.compareTo(namedOther.name); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.quote(name); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java index d910374f1e0f..816cd15b9224 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationParser.java @@ -27,24 +27,29 @@ import java.net.URI; import java.util.Collections; import java.util.List; -import java.util.function.Consumer; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; /** * Parses JSON describing lists of interfaces and register them in the {@link DynamicProxyRegistry}. */ public final class ProxyConfigurationParser extends ConfigurationParser { - private final Consumer>> interfaceListConsumer; - public ProxyConfigurationParser(Consumer>> interfaceListConsumer, boolean strictConfiguration) { + private final ConfigurationConditionResolver conditionResolver; + + private final BiConsumer> proxyConfigConsumer; + + public ProxyConfigurationParser(boolean strictConfiguration, BiConsumer> proxyConfigConsumer) { super(strictConfiguration); - this.interfaceListConsumer = interfaceListConsumer; + this.proxyConfigConsumer = proxyConfigConsumer; + this.conditionResolver = ConfigurationConditionResolver.identityResolver(); } @Override @@ -58,7 +63,7 @@ private void parseTopLevelArray(List proxyConfiguration) { for (Object proxyConfigurationObject : proxyConfiguration) { if (proxyConfigurationObject instanceof List) { foundInterfaceLists = true; - parseInterfaceList(ConfigurationCondition.alwaysTrue(), asList(proxyConfigurationObject, "")); + parseInterfaceList(conditionResolver.alwaysTrue(), asList(proxyConfigurationObject, "")); } else if (proxyConfigurationObject instanceof EconomicMap) { foundProxyConfigurationObjects = true; parseWithConditionalConfig(asMap(proxyConfigurationObject, "")); @@ -75,7 +80,7 @@ private void parseInterfaceList(ConfigurationCondition condition, List data) List interfaces = data.stream().map(ConfigurationParser::asString).collect(Collectors.toList()); try { - interfaceListConsumer.accept(new ConditionalElement<>(condition, interfaces)); + proxyConfigConsumer.accept(condition, interfaces); } catch (Exception e) { throw new JSONParserException(e.toString()); } @@ -83,7 +88,10 @@ private void parseInterfaceList(ConfigurationCondition condition, List data) private void parseWithConditionalConfig(EconomicMap proxyConfigObject) { checkAttributes(proxyConfigObject, "proxy descriptor object", Collections.singleton("interfaces"), Collections.singletonList(CONDITIONAL_KEY)); - ConfigurationCondition condition = parseCondition(proxyConfigObject); - parseInterfaceList(condition, asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names")); + ConfigurationCondition condition = parseCondition(proxyConfigObject, false); + TypeResult resolvedCondition = conditionResolver.resolveCondition(condition); + if (resolvedCondition.isPresent()) { + parseInterfaceList(resolvedCondition.get(), asList(proxyConfigObject.get("interfaces"), "The interfaces property must be an array of fully qualified interface names")); + } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java new file mode 100644 index 000000000000..a085ae2ab252 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ProxyConfigurationTypeDescriptor.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +import com.oracle.svm.core.reflect.proxy.DynamicProxySupport; +import com.oracle.svm.core.util.json.JsonPrinter; +import com.oracle.svm.core.util.json.JsonWriter; + +public record ProxyConfigurationTypeDescriptor(List interfaceNames) implements ConfigurationTypeDescriptor { + + public ProxyConfigurationTypeDescriptor(List interfaceNames) { + this.interfaceNames = interfaceNames.stream().map(ConfigurationTypeDescriptor::checkQualifiedJavaName).toList(); + } + + @Override + public Kind getDescriptorType() { + return Kind.PROXY; + } + + @Override + public String toString() { + return DynamicProxySupport.proxyTypeDescriptor(interfaceNames.toArray(String[]::new)); + } + + @Override + public Collection getAllQualifiedJavaNames() { + return interfaceNames; + } + + @Override + public int compareTo(ConfigurationTypeDescriptor other) { + if (other instanceof ProxyConfigurationTypeDescriptor proxyOther) { + return Arrays.compare(interfaceNames.toArray(String[]::new), proxyOther.interfaceNames.toArray(String[]::new)); + } else { + return getDescriptorType().compareTo(other.getDescriptorType()); + } + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart(); + writer.quote("proxy").appendFieldSeparator(); + JsonPrinter.printCollection(writer, interfaceNames, null, (String p, JsonWriter w) -> w.quote(p)); + writer.appendObjectEnd(); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java index 166a67838b48..202b6c3c2669 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -32,7 +31,6 @@ import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; @@ -43,182 +41,61 @@ * Parses JSON describing classes, methods and fields and delegates their registration to a * {@link ReflectionConfigurationParserDelegate}. */ -public final class ReflectionConfigurationParser extends ConfigurationParser { +public abstract class ReflectionConfigurationParser extends ConfigurationParser { private static final String CONSTRUCTOR_NAME = ""; - private final ReflectionConfigurationParserDelegate delegate; - private static final List OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS = Arrays.asList("allDeclaredConstructors", "allPublicConstructors", - "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", - "allDeclaredClasses", "allRecordComponents", "allPermittedSubclasses", "allNestMembers", "allSigners", - "allPublicClasses", "methods", "queriedMethods", "fields", CONDITIONAL_KEY, - "queryAllDeclaredConstructors", "queryAllPublicConstructors", "queryAllDeclaredMethods", "queryAllPublicMethods", "unsafeAllocated"); + protected final ConfigurationConditionResolver conditionResolver; + protected final ReflectionConfigurationParserDelegate delegate; private final boolean printMissingElements; - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate) { - this(delegate, true, false); - } - - public ReflectionConfigurationParser(ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, boolean printMissingElements) { + public ReflectionConfigurationParser(ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { super(strictConfiguration); + this.conditionResolver = conditionResolver; this.printMissingElements = printMissingElements; this.delegate = delegate; } - @Override - public void parseAndRegister(Object json, URI origin) { - parseClassArray(asList(json, "first level of document must be an array of class descriptors")); + public static ReflectionConfigurationParser create(String combinedFileKey, boolean strictMetadata, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { + if (strictMetadata) { + return new ReflectionMetadataParser<>(combinedFileKey, ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements); + } else { + return new LegacyReflectionConfigurationParser<>(ConfigurationConditionResolver.identityResolver(), delegate, strictConfiguration, printMissingElements, false); + } } - private void parseClassArray(List classes) { + protected void parseClassArray(List classes) { for (Object clazz : classes) { parseClass(asMap(clazz, "second level of document must be class descriptor objects")); } } - private void parseClass(EconomicMap data) { - checkAttributes(data, "reflection class descriptor object", Collections.singleton("name"), OPTIONAL_REFLECT_CONFIG_OBJECT_ATTRS); - - Object classObject = data.get("name"); - String className = asString(classObject, "name"); + protected abstract void parseClass(EconomicMap data); - TypeResult conditionResult = delegate.resolveCondition(parseCondition(data).getTypeName()); - if (!conditionResult.isPresent()) { - return; - } - ConfigurationCondition condition = conditionResult.get(); - - /* - * Even if primitives cannot be queried through Class.forName, they can be registered to - * allow getDeclaredMethods() and similar bulk queries at run time. - */ - TypeResult result = delegate.resolveType(condition, className, true); - if (!result.isPresent()) { - handleMissingElement("Could not resolve class " + className + " for reflection configuration.", result.getException()); - return; - } - T clazz = result.get(); - delegate.registerType(clazz); - - MapCursor cursor = data.getEntries(); - while (cursor.advance()) { - String name = cursor.getKey(); - Object value = cursor.getValue(); + protected void registerIfNotDefault(EconomicMap data, boolean defaultValue, T clazz, String propertyName, Runnable register) { + if (data.containsKey(propertyName) ? asBoolean(data.get(propertyName), propertyName) : defaultValue) { try { - switch (name) { - case "allDeclaredConstructors": - if (asBoolean(value, "allDeclaredConstructors")) { - delegate.registerDeclaredConstructors(false, clazz); - } - break; - case "allPublicConstructors": - if (asBoolean(value, "allPublicConstructors")) { - delegate.registerPublicConstructors(false, clazz); - } - break; - case "allDeclaredMethods": - if (asBoolean(value, "allDeclaredMethods")) { - delegate.registerDeclaredMethods(false, clazz); - } - break; - case "allPublicMethods": - if (asBoolean(value, "allPublicMethods")) { - delegate.registerPublicMethods(false, clazz); - } - break; - case "allDeclaredFields": - if (asBoolean(value, "allDeclaredFields")) { - delegate.registerDeclaredFields(clazz); - } - break; - case "allPublicFields": - if (asBoolean(value, "allPublicFields")) { - delegate.registerPublicFields(clazz); - } - break; - case "allDeclaredClasses": - if (asBoolean(value, "allDeclaredClasses")) { - delegate.registerDeclaredClasses(clazz); - } - break; - case "allRecordComponents": - if (asBoolean(value, "allRecordComponents")) { - delegate.registerRecordComponents(clazz); - } - break; - case "allPermittedSubclasses": - if (asBoolean(value, "allPermittedSubclasses")) { - delegate.registerPermittedSubclasses(clazz); - } - break; - case "allNestMembers": - if (asBoolean(value, "allNestMembers")) { - delegate.registerNestMembers(clazz); - } - break; - case "allSigners": - if (asBoolean(value, "allSigners")) { - delegate.registerSigners(clazz); - } - break; - case "allPublicClasses": - if (asBoolean(value, "allPublicClasses")) { - delegate.registerPublicClasses(clazz); - } - break; - case "queryAllDeclaredConstructors": - if (asBoolean(value, "queryAllDeclaredConstructors")) { - delegate.registerDeclaredConstructors(true, clazz); - } - break; - case "queryAllPublicConstructors": - if (asBoolean(value, "queryAllPublicConstructors")) { - delegate.registerPublicConstructors(true, clazz); - } - break; - case "queryAllDeclaredMethods": - if (asBoolean(value, "queryAllDeclaredMethods")) { - delegate.registerDeclaredMethods(true, clazz); - } - break; - case "queryAllPublicMethods": - if (asBoolean(value, "queryAllPublicMethods")) { - delegate.registerPublicMethods(true, clazz); - } - break; - case "unsafeAllocated": - if (asBoolean(value, "unsafeAllocated")) { - delegate.registerUnsafeAllocated(clazz); - } - break; - case "methods": - parseMethods(false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); - break; - case "queriedMethods": - parseMethods(true, asList(value, "Attribute 'queriedMethods' must be an array of method descriptors"), clazz); - break; - case "fields": - parseFields(asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); - break; - } + register.run(); } catch (LinkageError e) { - handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + propertyName + " for reflection.", e); } } } - private void parseFields(List fields, T clazz) { + protected void parseFields(ConfigurationCondition condition, List fields, T clazz) { for (Object field : fields) { - parseField(asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); + parseField(condition, asMap(field, "Elements of 'fields' array must be field descriptor objects"), clazz); } } - private void parseField(EconomicMap data, T clazz) { + private void parseField(ConfigurationCondition condition, EconomicMap data, T clazz) { checkAttributes(data, "reflection field descriptor object", Collections.singleton("name"), Arrays.asList("allowWrite", "allowUnsafeAccess")); String fieldName = asString(data.get("name"), "name"); boolean allowWrite = data.containsKey("allowWrite") && asBoolean(data.get("allowWrite"), "allowWrite"); try { - delegate.registerField(clazz, fieldName, allowWrite); + delegate.registerField(condition, clazz, fieldName, allowWrite); } catch (NoSuchFieldException e) { handleMissingElement("Field " + formatField(clazz, fieldName) + " not found."); } catch (LinkageError e) { @@ -226,13 +103,13 @@ private void parseField(EconomicMap data, T clazz) { } } - private void parseMethods(boolean queriedOnly, List methods, T clazz) { + protected void parseMethods(ConfigurationCondition condition, boolean queriedOnly, List methods, T clazz) { for (Object method : methods) { - parseMethod(queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); + parseMethod(condition, queriedOnly, asMap(method, "Elements of 'methods' array must be method descriptor objects"), clazz); } } - private void parseMethod(boolean queriedOnly, EconomicMap data, T clazz) { + private void parseMethod(ConfigurationCondition condition, boolean queriedOnly, EconomicMap data, T clazz) { checkAttributes(data, "reflection method descriptor object", Collections.singleton("name"), Collections.singleton("parameterTypes")); String methodName = asString(data.get("name"), "name"); List methodParameterTypes = null; @@ -248,9 +125,9 @@ private void parseMethod(boolean queriedOnly, EconomicMap data, if (methodParameterTypes != null) { try { if (isConstructor) { - delegate.registerConstructor(queriedOnly, clazz, methodParameterTypes); + delegate.registerConstructor(condition, queriedOnly, clazz, methodParameterTypes); } else { - delegate.registerMethod(queriedOnly, clazz, methodName, methodParameterTypes); + delegate.registerMethod(condition, queriedOnly, clazz, methodName, methodParameterTypes); } } catch (NoSuchMethodException e) { handleMissingElement("Method " + formatMethod(clazz, methodName, methodParameterTypes) + " not found."); @@ -261,9 +138,9 @@ private void parseMethod(boolean queriedOnly, EconomicMap data, try { boolean found; if (isConstructor) { - found = delegate.registerAllConstructors(queriedOnly, clazz); + found = delegate.registerAllConstructors(condition, queriedOnly, clazz); } else { - found = delegate.registerAllMethodsWithName(queriedOnly, clazz, methodName); + found = delegate.registerAllMethodsWithName(condition, queriedOnly, clazz, methodName); } if (!found) { throw new JSONParserException("Method " + formatMethod(clazz, methodName) + " not found"); @@ -278,7 +155,7 @@ private List parseMethodParameters(T clazz, String methodName, List t List result = new ArrayList<>(); for (Object type : types) { String typeName = asString(type, "types"); - TypeResult typeResult = delegate.resolveType(ConfigurationCondition.alwaysTrue(), typeName, true); + TypeResult typeResult = delegate.resolveType(conditionResolver.alwaysTrue(), new NamedConfigurationTypeDescriptor(typeName), true); if (!typeResult.isPresent()) { handleMissingElement("Could not register method " + formatMethod(clazz, methodName) + " for reflection.", typeResult.getException()); return null; @@ -309,7 +186,7 @@ private void handleMissingElement(String message) { handleMissingElement(message, null); } - private void handleMissingElement(String msg, Throwable cause) { + protected void handleMissingElement(String msg, Throwable cause) { if (printMissingElements) { String message = msg; if (cause != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java index 7cbce8f012f5..417080a2f19f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionConfigurationParserDelegate.java @@ -32,47 +32,45 @@ public interface ReflectionConfigurationParserDelegate { - TypeResult resolveCondition(String typeName); + TypeResult resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives); - TypeResult resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives); + void registerType(ConfigurationCondition condition, T type); - void registerType(T type); + void registerPublicClasses(ConfigurationCondition condition, T type); - void registerPublicClasses(T type); + void registerDeclaredClasses(ConfigurationCondition condition, T type); - void registerDeclaredClasses(T type); + void registerRecordComponents(ConfigurationCondition condition, T type); - void registerRecordComponents(T type); + void registerPermittedSubclasses(ConfigurationCondition condition, T type); - void registerPermittedSubclasses(T type); + void registerNestMembers(ConfigurationCondition condition, T type); - void registerNestMembers(T type); + void registerSigners(ConfigurationCondition condition, T type); - void registerSigners(T type); + void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicFields(T type); + void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredFields(T type); + void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicMethods(boolean queriedOnly, T type); + void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredMethods(boolean queriedOnly, T type); + void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerPublicConstructors(boolean queriedOnly, T type); + void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - void registerDeclaredConstructors(boolean queriedOnly, T type); + void registerField(ConfigurationCondition condition, T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; - void registerField(T type, String fieldName, boolean allowWrite) throws NoSuchFieldException; + boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName); - boolean registerAllMethodsWithName(boolean queriedOnly, T type, String methodName); + void registerMethod(ConfigurationCondition condition, boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; - void registerMethod(boolean queriedOnly, T type, String methodName, List methodParameterTypes) throws NoSuchMethodException; + void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; - void registerConstructor(boolean queriedOnly, T type, List methodParameterTypes) throws NoSuchMethodException; + boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, T type); - boolean registerAllConstructors(boolean queriedOnly, T type); - - void registerUnsafeAllocated(T clazz); + void registerUnsafeAllocated(ConfigurationCondition condition, T clazz); String getTypeName(T type); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java new file mode 100644 index 000000000000..7b4fa47a4533 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ReflectionMetadataParser.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.core.TypeResult; + +class ReflectionMetadataParser extends ReflectionConfigurationParser { + private static final List OPTIONAL_REFLECT_METADATA_ATTRS = Arrays.asList(CONDITIONAL_KEY, + "allDeclaredConstructors", "allPublicConstructors", "allDeclaredMethods", "allPublicMethods", "allDeclaredFields", "allPublicFields", + "methods", "fields", "unsafeAllocated"); + + private final String combinedFileKey; + + ReflectionMetadataParser(String combinedFileKey, ConfigurationConditionResolver conditionResolver, ReflectionConfigurationParserDelegate delegate, boolean strictConfiguration, + boolean printMissingElements) { + super(conditionResolver, delegate, strictConfiguration, printMissingElements); + this.combinedFileKey = combinedFileKey; + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object reflectionJson = getFromGlobalFile(json, combinedFileKey); + if (reflectionJson != null) { + parseClassArray(asList(reflectionJson, "first level of document must be an array of class descriptors")); + } + } + + @Override + protected void parseClass(EconomicMap data) { + checkAttributes(data, "reflection class descriptor object", List.of(TYPE_KEY), OPTIONAL_REFLECT_METADATA_ATTRS); + + Optional type = parseTypeContents(data.get(TYPE_KEY)); + if (type.isEmpty()) { + return; + } + + ConfigurationCondition unresolvedCondition = parseCondition(data, true); + TypeResult conditionResult = conditionResolver.resolveCondition(unresolvedCondition); + if (!conditionResult.isPresent()) { + return; + } + ConfigurationCondition condition = conditionResult.get(); + + /* + * Even if primitives cannot be queried through Class.forName, they can be registered to + * allow getDeclaredMethods() and similar bulk queries at run time. + */ + TypeResult result = delegate.resolveType(condition, type.get(), true); + if (!result.isPresent()) { + handleMissingElement("Could not resolve " + type.get() + " for reflection configuration.", result.getException()); + return; + } + + ConfigurationCondition queryCondition = conditionResolver.alwaysTrue(); + T clazz = result.get(); + delegate.registerType(conditionResult.get(), clazz); + + delegate.registerDeclaredClasses(queryCondition, clazz); + delegate.registerRecordComponents(queryCondition, clazz); + delegate.registerPermittedSubclasses(queryCondition, clazz); + delegate.registerNestMembers(queryCondition, clazz); + delegate.registerSigners(queryCondition, clazz); + delegate.registerPublicClasses(queryCondition, clazz); + delegate.registerDeclaredConstructors(queryCondition, true, clazz); + delegate.registerPublicConstructors(queryCondition, true, clazz); + delegate.registerDeclaredMethods(queryCondition, true, clazz); + delegate.registerPublicMethods(queryCondition, true, clazz); + delegate.registerDeclaredFields(queryCondition, true, clazz); + delegate.registerPublicFields(queryCondition, true, clazz); + + registerIfNotDefault(data, false, clazz, "allDeclaredConstructors", () -> delegate.registerDeclaredConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicConstructors", () -> delegate.registerPublicConstructors(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredMethods", () -> delegate.registerDeclaredMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicMethods", () -> delegate.registerPublicMethods(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allDeclaredFields", () -> delegate.registerDeclaredFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "allPublicFields", () -> delegate.registerPublicFields(condition, false, clazz)); + registerIfNotDefault(data, false, clazz, "unsafeAllocated", () -> delegate.registerUnsafeAllocated(condition, clazz)); + + MapCursor cursor = data.getEntries(); + while (cursor.advance()) { + String name = cursor.getKey(); + Object value = cursor.getValue(); + try { + switch (name) { + case "methods": + parseMethods(condition, false, asList(value, "Attribute 'methods' must be an array of method descriptors"), clazz); + break; + case "fields": + parseFields(condition, asList(value, "Attribute 'fields' must be an array of field descriptors"), clazz); + break; + } + } catch (LinkageError e) { + handleMissingElement("Could not register " + delegate.getTypeName(clazz) + ": " + name + " for reflection.", e); + } + } + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java index 5ebcc0d4e463..237d9e964c10 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceConfigurationParser.java @@ -24,76 +24,45 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; -import java.util.function.BiConsumer; +import java.util.regex.Pattern; import java.util.stream.Collectors; import org.graalvm.collections.EconomicMap; -import org.graalvm.collections.MapCursor; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.util.json.JSONParserException; +import com.oracle.svm.core.TypeResult; import com.oracle.svm.core.jdk.localization.LocalizationSupport; -public class ResourceConfigurationParser extends ConfigurationParser { - private final ResourcesRegistry registry; +public abstract class ResourceConfigurationParser extends ConfigurationParser { + protected final ResourcesRegistry registry; - public ResourceConfigurationParser(ResourcesRegistry registry, boolean strictConfiguration) { + protected final ConfigurationConditionResolver conditionResolver; + + public static ResourceConfigurationParser create(boolean strictMetadata, ResourcesRegistry registry, boolean strictConfiguration) { + if (strictMetadata) { + return new ResourceMetadataParser(ConfigurationConditionResolver.identityResolver(), registry, strictConfiguration); + } else { + return new LegacyResourceConfigurationParser(ConfigurationConditionResolver.identityResolver(), registry, strictConfiguration); + } + } + + protected ResourceConfigurationParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { super(strictConfiguration); this.registry = registry; + this.conditionResolver = conditionResolver; } - @Override - public void parseAndRegister(Object json, URI origin) { - parseTopLevelObject(asMap(json, "first level of document must be an object")); - } + protected abstract ConfigurationCondition parseCondition(EconomicMap data); - @SuppressWarnings("unchecked") - private void parseTopLevelObject(EconomicMap obj) { - Object resourcesObject = null; - Object bundlesObject = null; - MapCursor cursor = obj.getEntries(); - while (cursor.advance()) { - if ("resources".equals(cursor.getKey())) { - resourcesObject = cursor.getValue(); - } else if ("bundles".equals(cursor.getKey())) { - bundlesObject = cursor.getValue(); - } - } - if (resourcesObject != null) { - if (resourcesObject instanceof EconomicMap) { // New format - EconomicMap resourcesObjectMap = (EconomicMap) resourcesObject; - checkAttributes(resourcesObjectMap, "resource descriptor object", Collections.singleton("includes"), Collections.singleton("excludes")); - Object includesObject = resourcesObjectMap.get("includes"); - Object excludesObject = resourcesObjectMap.get("excludes"); - - List includes = asList(includesObject, "Attribute 'includes' must be a list of resources"); - for (Object object : includes) { - parseStringEntry(object, "pattern", registry::addResources, "resource descriptor object", "'includes' list"); - } - - if (excludesObject != null) { - List excludes = asList(excludesObject, "Attribute 'excludes' must be a list of resources"); - for (Object object : excludes) { - parseStringEntry(object, "pattern", registry::ignoreResources, "resource descriptor object", "'excludes' list"); - } - } - } else { // Old format: may be deprecated in future versions - List resources = asList(resourcesObject, "Attribute 'resources' must be a list of resources"); - for (Object object : resources) { - parseStringEntry(object, "pattern", registry::addResources, "resource descriptor object", "'resources' list"); - } - } - } - if (bundlesObject != null) { - List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); - for (Object bundle : bundles) { - parseBundle(bundle); - } + protected void parseBundlesObject(Object bundlesObject) { + List bundles = asList(bundlesObject, "Attribute 'bundles' must be a list of bundles"); + for (Object bundle : bundles) { + parseBundle(bundle); } } @@ -101,7 +70,10 @@ private void parseBundle(Object bundle) { EconomicMap resource = asMap(bundle, "Elements of 'bundles' list must be a bundle descriptor object"); checkAttributes(resource, "bundle descriptor object", Collections.singletonList("name"), Arrays.asList("locales", "classNames", "condition")); String basename = asString(resource.get("name")); - ConfigurationCondition condition = parseCondition(resource); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(resource)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } Object locales = resource.get("locales"); if (locales != null) { List asList = asList(locales, "Attribute 'locales' must be a list of locales") @@ -109,7 +81,7 @@ private void parseBundle(Object bundle) { .map(ResourceConfigurationParser::parseLocale) .collect(Collectors.toList()); if (!asList.isEmpty()) { - registry.addResourceBundles(condition, basename, asList); + registry.addResourceBundles(resolvedConfigurationCondition.get(), basename, asList); } } @@ -118,12 +90,12 @@ private void parseBundle(Object bundle) { List asList = asList(classNames, "Attribute 'classNames' must be a list of classes"); for (Object o : asList) { String className = asString(o); - registry.addClassBasedResourceBundle(condition, basename, className); + registry.addClassBasedResourceBundle(resolvedConfigurationCondition.get(), basename, className); } } if (locales == null && classNames == null) { /* If nothing more precise is specified, register in every included locale */ - registry.addResourceBundles(condition, basename); + registry.addResourceBundles(resolvedConfigurationCondition.get(), basename); } } @@ -136,12 +108,106 @@ private static Locale parseLocale(Object input) { return locale; } - private void parseStringEntry(Object data, String valueKey, BiConsumer resourceRegistry, String expectedType, String parentType) { - EconomicMap resource = asMap(data, "Elements of " + parentType + " must be a " + expectedType); - checkAttributes(resource, "resource and resource bundle descriptor object", Collections.singletonList(valueKey), Collections.singletonList(CONDITIONAL_KEY)); - ConfigurationCondition condition = parseCondition(resource); - Object valueObject = resource.get(valueKey); - String value = asString(valueObject, valueKey); - resourceRegistry.accept(condition, value); + public static String globToRegex(String module, String glob) { + return (module == null || module.isEmpty() ? "" : module + ":") + globToRegex(glob); + } + + private static String globToRegex(String glob) { + /* this char will trigger last wildcard dump if the glob ends with the wildcard */ + String tmpGlob = glob + '#'; + StringBuilder sb = new StringBuilder(); + + int quoteStartIndex = 0; + Wildcard previousWildcard = Wildcard.START; + for (int i = 0; i < tmpGlob.length(); i++) { + char c = tmpGlob.charAt(i); + Wildcard currentWildcard = previousWildcard.next(c); + + boolean wildcardStart = previousWildcard == Wildcard.START && currentWildcard != Wildcard.START; + if (wildcardStart && quoteStartIndex != i) { + /* start of the new wildcard => quote previous content */ + sb.append(Pattern.quote(tmpGlob.substring(quoteStartIndex, i))); + } + + boolean consecutiveWildcards = previousWildcard == Wildcard.DOUBLE_STAR_SLASH && currentWildcard != Wildcard.START; + boolean wildcardEnd = previousWildcard != Wildcard.START && currentWildcard == Wildcard.START; + if (wildcardEnd || consecutiveWildcards) { + /* end of the wildcard => append regex and move start of next quote after it */ + sb.append(previousWildcard.regex); + quoteStartIndex = i; + } + previousWildcard = currentWildcard; + } + /* remove the last char we added artificially */ + tmpGlob = tmpGlob.substring(0, tmpGlob.length() - 1); + if (quoteStartIndex < tmpGlob.length()) { + sb.append(Pattern.quote(tmpGlob.substring(quoteStartIndex))); + } + return sb.toString(); + } + + /** + * This enum acts like a state machine that helps to identify glob wildcards. + */ + private enum Wildcard { + START("") { + @Override + public Wildcard next(char c) { + return c == '*' ? STAR : START; + } + }, + STAR("[^/]*") { + @Override + public Wildcard next(char c) { + return c == '*' ? DOUBLE_STAR : START; + } + }, + DOUBLE_STAR(".*") { + @Override + public Wildcard next(char c) { + return c == '/' ? DOUBLE_STAR_SLASH : START; + } + }, + DOUBLE_STAR_SLASH("([^/]*(/|$))*") { + @Override + public Wildcard next(char c) { + return c == '*' ? STAR : START; + } + }; + + final String regex; + + Wildcard(String val) { + regex = val; + } + + public abstract Wildcard next(char c); + } + + protected void parseGlobsObject(Object globsObject) { + List globs = asList(globsObject, "Attribute 'globs' must be a list of glob patterns"); + for (Object object : globs) { + parseGlobEntry(object, (condition, module, resource) -> registry.addResources(condition, globToRegex(module, resource))); + } + } + + private interface GlobPatternConsumer { + void accept(T a, String b, String c); + } + + private void parseGlobEntry(Object data, GlobPatternConsumer resourceRegistry) { + EconomicMap globObject = asMap(data, "Elements of 'globs' list must be a glob descriptor objects"); + checkAttributes(globObject, "glob resource descriptor object", Collections.singletonList(GLOB_KEY), List.of(CONDITIONAL_KEY, MODULE_KEY)); + TypeResult resolvedConfigurationCondition = conditionResolver.resolveCondition(parseCondition(globObject)); + if (!resolvedConfigurationCondition.isPresent()) { + return; + } + + Object moduleObject = globObject.get(MODULE_KEY); + String module = asNullableString(moduleObject, MODULE_KEY); + + Object valueObject = globObject.get(GLOB_KEY); + String value = asString(valueObject, GLOB_KEY); + resourceRegistry.accept(resolvedConfigurationCondition.get(), module, value); } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java new file mode 100644 index 000000000000..caa09ce98cf3 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourceMetadataParser.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +final class ResourceMetadataParser extends ResourceConfigurationParser { + ResourceMetadataParser(ConfigurationConditionResolver conditionResolver, ResourcesRegistry registry, boolean strictConfiguration) { + super(conditionResolver, registry, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object resourcesJson = getFromGlobalFile(json, RESOURCES_KEY); + if (resourcesJson != null) { + parseGlobsObject(resourcesJson); + } + Object bundlesJson = getFromGlobalFile(json, BUNDLES_KEY); + if (bundlesJson != null) { + parseBundlesObject(bundlesJson); + } + } + + @Override + protected ConfigurationCondition parseCondition(EconomicMap data) { + return parseCondition(data, true); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java index df941c65f45c..310f25d43b07 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/ResourcesRegistry.java @@ -27,38 +27,15 @@ import java.util.Collection; import java.util.Locale; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeResourceSupport; public interface ResourcesRegistry extends RuntimeResourceSupport { - /** - * @deprecated Use {@link RuntimeResourceSupport#addResources(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void addResources(String pattern) { - addResources(ConfigurationCondition.alwaysTrue(), pattern); - } - - /** - * @deprecated Use - * {@link RuntimeResourceSupport#ignoreResources(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void ignoreResources(String pattern) { - ignoreResources(ConfigurationCondition.alwaysTrue(), pattern); - } - - /** - * @deprecated Use - * {@link RuntimeResourceSupport#addResourceBundles(ConfigurationCondition, String)} - * instead. - */ - @Deprecated - default void addResourceBundles(String name) { - addResourceBundles(ConfigurationCondition.alwaysTrue(), name); + @SuppressWarnings("unchecked") + static ResourcesRegistry singleton() { + return ImageSingletons.lookup(ResourcesRegistry.class); } void addClassBasedResourceBundle(ConfigurationCondition condition, String basename, String className); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java index ff497052314b..1f26a9e072ac 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationConfigurationParser.java @@ -25,9 +25,6 @@ */ package com.oracle.svm.core.configure; -import java.net.URI; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import org.graalvm.collections.EconomicMap; @@ -35,76 +32,43 @@ import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; import org.graalvm.util.json.JSONParserException; -public class SerializationConfigurationParser extends ConfigurationParser { +public abstract class SerializationConfigurationParser extends ConfigurationParser { - public static final String NAME_KEY = "name"; public static final String CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY = "customTargetConstructorClass"; - private static final String SERIALIZATION_TYPES_KEY = "types"; - private static final String LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY = "lambdaCapturingTypes"; - private static final String PROXY_SERIALIZATION_TYPES_KEY = "proxies"; - private final RuntimeSerializationSupport serializationSupport; - private final ProxyConfigurationParser proxyConfigurationParser; - public SerializationConfigurationParser(RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { - super(strictConfiguration); - this.serializationSupport = serializationSupport; - this.proxyConfigurationParser = new ProxyConfigurationParser( - (conditionalElement) -> serializationSupport.registerProxyClass(conditionalElement.getCondition(), conditionalElement.getElement()), strictConfiguration); - } + protected final ConfigurationConditionResolver conditionResolver; + protected final RuntimeSerializationSupport serializationSupport; - @Override - public void parseAndRegister(Object json, URI origin) { - if (json instanceof List) { - parseOldConfiguration(asList(json, "First-level of document must be an array of serialization lists")); - } else if (json instanceof EconomicMap) { - parseNewConfiguration(asMap(json, "First-level of document must be a map of serialization types")); + public static SerializationConfigurationParser create(boolean strictMetadata, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + if (strictMetadata) { + return new SerializationMetadataParser<>(ConfigurationConditionResolver.identityResolver(), serializationSupport, strictConfiguration); } else { - throw new JSONParserException("First-level of document must either be an array of serialization lists or a map of serialization types"); + return new LegacySerializationConfigurationParser(ConfigurationConditionResolver.identityResolver(), serializationSupport, strictConfiguration); } } - private void parseOldConfiguration(List listOfSerializationConfigurationObjects) { - parseSerializationTypes(asList(listOfSerializationConfigurationObjects, "Second-level of document must be serialization descriptor objects"), false); - } - - private void parseNewConfiguration(EconomicMap listOfSerializationConfigurationObjects) { - if (!listOfSerializationConfigurationObjects.containsKey(SERIALIZATION_TYPES_KEY) || !listOfSerializationConfigurationObjects.containsKey(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY)) { - throw new JSONParserException("Second-level of document must be arrays of serialization descriptor objects"); - } - - parseSerializationTypes(asList(listOfSerializationConfigurationObjects.get(SERIALIZATION_TYPES_KEY), "The types property must be an array of serialization descriptor objects"), false); - parseSerializationTypes( - asList(listOfSerializationConfigurationObjects.get(LAMBDA_CAPTURING_SERIALIZATION_TYPES_KEY), - "The lambdaCapturingTypes property must be an array of serialization descriptor objects"), - true); - - if (listOfSerializationConfigurationObjects.containsKey(PROXY_SERIALIZATION_TYPES_KEY)) { - proxyConfigurationParser.parseAndRegister(listOfSerializationConfigurationObjects.get(PROXY_SERIALIZATION_TYPES_KEY), null); - } + public SerializationConfigurationParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(strictConfiguration); + this.serializationSupport = serializationSupport; + this.conditionResolver = conditionResolver; } - private void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { + protected void parseSerializationTypes(List listOfSerializationTypes, boolean lambdaCapturingTypes) { for (Object serializationType : listOfSerializationTypes) { parseSerializationDescriptorObject(asMap(serializationType, "Third-level of document must be serialization descriptor objects"), lambdaCapturingTypes); } } - private void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { - if (lambdaCapturingType) { - checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Collections.singleton(CONDITIONAL_KEY)); - } else { - checkAttributes(data, "serialization descriptor object", Collections.singleton(NAME_KEY), Arrays.asList(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY, CONDITIONAL_KEY)); - } - - ConfigurationCondition unresolvedCondition = parseCondition(data); - String targetSerializationClass = asString(data.get(NAME_KEY)); + protected abstract void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType); - if (lambdaCapturingType) { - serializationSupport.registerLambdaCapturingClass(unresolvedCondition, targetSerializationClass); + protected void registerType(ConfigurationTypeDescriptor targetSerializationClass, ConfigurationCondition condition, Object optionalCustomCtorValue) { + String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; + if (targetSerializationClass instanceof NamedConfigurationTypeDescriptor namedClass) { + serializationSupport.registerWithTargetConstructorClass(condition, namedClass.name(), customTargetConstructorClass); + } else if (targetSerializationClass instanceof ProxyConfigurationTypeDescriptor proxyClass) { + serializationSupport.registerProxyClass(condition, proxyClass.interfaceNames()); } else { - Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); - String customTargetConstructorClass = optionalCustomCtorValue != null ? asString(optionalCustomCtorValue) : null; - serializationSupport.registerWithTargetConstructorClass(unresolvedCondition, targetSerializationClass, customTargetConstructorClass); + throw new JSONParserException("Unknown configuration type descriptor: %s".formatted(targetSerializationClass.toString())); } } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java new file mode 100644 index 000000000000..86d81982df34 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/SerializationMetadataParser.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package com.oracle.svm.core.configure; + +import java.net.URI; +import java.util.List; +import java.util.Optional; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; +import org.graalvm.nativeimage.impl.RuntimeSerializationSupport; + +final class SerializationMetadataParser extends SerializationConfigurationParser { + + SerializationMetadataParser(ConfigurationConditionResolver conditionResolver, RuntimeSerializationSupport serializationSupport, boolean strictConfiguration) { + super(conditionResolver, serializationSupport, strictConfiguration); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + Object serializationJson = getFromGlobalFile(json, SERIALIZATION_KEY); + if (serializationJson != null) { + parseSerializationTypes(asList(serializationJson, "The serialization property must be an array of serialization descriptor object"), false); + } + } + + @Override + protected void parseSerializationDescriptorObject(EconomicMap data, boolean lambdaCapturingType) { + checkAttributes(data, "serialization descriptor object", List.of(TYPE_KEY), List.of(CONDITIONAL_KEY, CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY)); + + Optional targetSerializationClass = parseTypeContents(data.get(TYPE_KEY)); + if (targetSerializationClass.isEmpty()) { + return; + } + + ConfigurationCondition unresolvedCondition = parseCondition(data, true); + var condition = conditionResolver.resolveCondition(unresolvedCondition); + if (!condition.isPresent()) { + return; + } + + Object optionalCustomCtorValue = data.get(CUSTOM_TARGET_CONSTRUCTOR_CLASS_KEY); + registerType(targetSerializationClass.get(), condition.get(), optionalCustomCtorValue); + } +} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt index cf0217313abe..a4841995909f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ProxyConfigurationFilesHelp.txt @@ -1,4 +1,5 @@ One or several (comma-separated) paths to JSON files that specify lists of interfaces that define Java proxy classes. + The JSON structure is described in the following schema: https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/proxy-config-schema-v1.0.0.json diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt index aaf1027fdb4b..cc20fd1e7d7f 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/ReflectionConfigurationFilesHelp.txt @@ -1,7 +1,8 @@ One or several (comma-separated) paths to JSON files that specify which program elements should be made available via reflection. + The JSON object schema is described at: - https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.0.0.json + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/reflect-config-schema-v1.1.0.json Example: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt index e199d657abc5..e606e1ede89c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/configure/doc-files/SerializationConfigurationFilesHelp.txt @@ -1,7 +1,8 @@ One or several (comma-separated) paths to JSON files that specify lists of serialization configurations. + The structure is described in the following schema: - https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.0.0.json + https://github.com/oracle/graal/blob/master/docs/reference-manual/native-image/assets/serialization-config-schema-v1.1.0.json Example: diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java index d57bd01e0b52..e5749235490e 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/proxy/DynamicProxyRegistry.java @@ -35,5 +35,5 @@ public interface DynamicProxyRegistry extends RuntimeProxyCreationSupport { boolean isProxyClass(Class clazz); @Platforms(Platform.HOSTED_ONLY.class) - Class createProxyClassForSerialization(Class... interfaces); + Class getProxyClassHosted(Class... interfaces); } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java index 5f28bed354b9..773038bc8e8c 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/methodhandles/Target_java_lang_invoke_MethodHandles_Lookup.java @@ -28,9 +28,11 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; +import java.util.concurrent.ConcurrentHashMap; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Alias; +import com.oracle.svm.core.annotate.Delete; import com.oracle.svm.core.annotate.RecomputeFieldValue; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; @@ -39,6 +41,11 @@ @TargetClass(value = MethodHandles.class, innerClass = "Lookup") final class Target_java_lang_invoke_MethodHandles_Lookup { + // Checkstyle: stop + @Delete // + static ConcurrentHashMap LOOKASIDE_TABLE; + // Checkstyle: resume + @SuppressWarnings("static-method") @Substitute public Class defineClass(@SuppressWarnings("unused") byte[] bytes) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java index 8ca8222d8ccd..305ec0c30eb9 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/MissingReflectionRegistrationUtils.java @@ -54,6 +54,17 @@ public static void forField(Class declaringClass, String fieldName) { report(exception); } + public static MissingReflectionRegistrationError errorForQueriedOnlyField(Field field) { + MissingReflectionRegistrationError exception = new MissingReflectionRegistrationError(errorMessage("read or write field", field.toString()), + field.getClass(), field.getDeclaringClass(), field.getName(), null); + report(exception); + /* + * If report doesn't throw, we throw the exception anyway since this is a Native + * Image-specific error that is unrecoverable in any case. + */ + return exception; + } + public static void forMethod(Class declaringClass, String methodName, Class[] paramTypes) { StringJoiner paramTypeNames = new StringJoiner(", ", "(", ")"); if (paramTypes != null) { diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java index b1a3d5f075af..8961f347adbf 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/proxy/DynamicProxySupport.java @@ -145,7 +145,7 @@ private Object createProxyClass(Class[] interfaces) { @Override @Platforms(Platform.HOSTED_ONLY.class) - public Class createProxyClassForSerialization(Class... interfaces) { + public Class getProxyClassHosted(Class... interfaces) { final Class[] intfs = interfaces.clone(); return createProxyClassFromImplementedInterfaces(intfs); } @@ -244,4 +244,8 @@ private synchronized boolean isHostedProxyClass(Class clazz) { public static Class getJdkProxyClass(ClassLoader loader, Class... interfaces) { return java.lang.reflect.Proxy.getProxyClass(loader, interfaces); } + + public static String proxyTypeDescriptor(String... interfaceNames) { + return "Proxy[" + String.join(", ", interfaceNames) + "]"; + } } diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java index 1fc5303ac94c..98ed7c9b61ee 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/reflect/target/Target_jdk_internal_misc_Unsafe_Reflection.java @@ -31,7 +31,7 @@ import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; -import com.oracle.svm.core.util.VMError; +import com.oracle.svm.core.reflect.MissingReflectionRegistrationUtils; @TargetClass(className = "jdk.internal.misc.Unsafe") @SuppressWarnings({"static-method"}) @@ -80,13 +80,10 @@ static long getFieldOffset(Target_java_lang_reflect_Field field) { throw new NullPointerException(); } int offset = field.root == null ? field.offset : field.root.offset; - if (offset > 0) { - return offset; + if (offset <= 0) { + throw MissingReflectionRegistrationUtils.errorForQueriedOnlyField(SubstrateUtil.cast(field, Field.class)); } - throw VMError.unsupportedFeature("The offset of " + field + " is accessed without the field being first registered as unsafe accessed. " + - "Please register the field as unsafe accessed. You can do so with a reflection configuration that " + - "contains an entry for the field with the attribute \"allowUnsafeAccess\": true. Such a configuration " + - "file can be generated for you. Read BuildConfiguration.md and Reflection.md for details."); + return offset; } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java index dab90726dfbf..1f9ef65a20e5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ConditionalConfigurationRegistry.java @@ -50,7 +50,9 @@ protected void registerConditionalConfiguration(ConfigurationCondition condition public void flushConditionalConfiguration(Feature.BeforeAnalysisAccess b) { for (Map.Entry> reachabilityEntry : pendingReachabilityHandlers.entrySet()) { TypeResult> typeResult = ((FeatureImpl.BeforeAnalysisAccessImpl) b).getImageClassLoader().findClass(reachabilityEntry.getKey()); - b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); + if (typeResult.isPresent()) { + b.registerReachabilityHandler(access -> reachabilityEntry.getValue().forEach(Runnable::run), typeResult.get()); + } } pendingReachabilityHandlers.clear(); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java index 5ce7637a13ac..ee51025bdb02 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/ResourcesFeature.java @@ -213,10 +213,14 @@ private static ResourcesRegistryImpl resourceRegistryImpl() { @Override public void beforeAnalysis(BeforeAnalysisAccess access) { - ResourceConfigurationParser parser = new ResourceConfigurationParser(ImageSingletons.lookup(ResourcesRegistry.class), ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "resource", - ConfigurationFiles.Options.ResourceConfigurationFiles, ConfigurationFiles.Options.ResourceConfigurationResources, - ConfigurationFile.RESOURCES.getFileName()); + ResourceConfigurationParser parser = ResourceConfigurationParser.create(true, ResourcesRegistry.singleton(), + ConfigurationFiles.Options.StrictConfiguration.getValue()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "resource"); + + ResourceConfigurationParser legacyParser = ResourceConfigurationParser.create(false, ResourcesRegistry.singleton(), + ConfigurationFiles.Options.StrictConfiguration.getValue()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "resource", ConfigurationFiles.Options.ResourceConfigurationFiles, + ConfigurationFiles.Options.ResourceConfigurationResources, ConfigurationFile.RESOURCES.getFileName()); resourcePatternWorkSet.addAll(Options.IncludeResources.getValue().values()); excludedResourcePatterns.addAll(Options.ExcludeResources.getValue().values()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java index fde6041471ee..98e01b52b720 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ConfigurationParserUtils.java @@ -30,6 +30,7 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collections; import java.util.Enumeration; import java.util.List; import java.util.Spliterator; @@ -41,7 +42,7 @@ import org.graalvm.nativeimage.impl.ReflectionRegistry; import org.graalvm.util.json.JSONParserException; -import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -52,10 +53,9 @@ public final class ConfigurationParserUtils { - public static ReflectionConfigurationParser>> create(ReflectionRegistry registry, ImageClassLoader imageClassLoader) { - return new ReflectionConfigurationParser<>(RegistryAdapter.create(registry, imageClassLoader), - ConfigurationFiles.Options.StrictConfiguration.getValue(), - ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue()); + public static ReflectionConfigurationParser> create(String combinedFileKey, boolean strictMetadata, ReflectionRegistry registry, ImageClassLoader imageClassLoader) { + return ReflectionConfigurationParser.create(combinedFileKey, strictMetadata, RegistryAdapter.create(registry, imageClassLoader), + ConfigurationFiles.Options.StrictConfiguration.getValue(), ConfigurationFiles.Options.WarnAboutMissingReflectionOrJNIMetadataElements.getValue()); } /** @@ -75,6 +75,10 @@ public static int parseAndRegisterConfigurations(ConfigurationParser parser, Ima return parseAndRegisterConfigurations(parser, classLoader, featureName, directoryFileName, paths, resourceValues); } + public static int parseAndRegisterConfigurationsFromCombinedFile(ConfigurationParser parser, ImageClassLoader classLoader, String featureName) { + return parseAndRegisterConfigurations(parser, classLoader, featureName, ConfigurationFile.REACHABILITY_METADATA.getFileName(), Collections.emptyList(), Collections.emptyList()); + } + public static int parseAndRegisterConfigurations(ConfigurationParser parser, ImageClassLoader classLoader, String featureName, String directoryFileName, List paths, diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java index 6b763eb4e8bd..c7bdb4043af5 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/ReflectionRegistryAdapter.java @@ -32,8 +32,9 @@ import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.reflect.ReflectionDataBuilder; public class ReflectionRegistryAdapter extends RegistryAdapter { private final RuntimeReflectionSupport reflectionSupport; @@ -44,86 +45,86 @@ public class ReflectionRegistryAdapter extends RegistryAdapter { } @Override - public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - TypeResult>> result = super.resolveType(condition, typeName, allowPrimitives); + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + TypeResult> result = super.resolveType(condition, typeDescriptor, allowPrimitives); if (!result.isPresent()) { Throwable classLookupException = result.getException(); if (classLookupException instanceof LinkageError) { - reflectionSupport.registerClassLookupException(condition, typeName, classLookupException); + reflectionSupport.registerClassLookupException(condition, typeDescriptor.toString(), classLookupException); } else if (throwMissingRegistrationErrors() && classLookupException instanceof ClassNotFoundException) { - reflectionSupport.registerClassLookup(condition, typeName); + reflectionSupport.registerClassLookup(condition, typeDescriptor.toString()); } } return result; } @Override - public void registerPublicClasses(ConditionalElement> type) { - reflectionSupport.registerAllClassesQuery(type.getCondition(), type.getElement()); + public void registerPublicClasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllClassesQuery(condition, type); } @Override - public void registerDeclaredClasses(ConditionalElement> type) { - reflectionSupport.registerAllDeclaredClassesQuery(type.getCondition(), type.getElement()); + public void registerDeclaredClasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllDeclaredClassesQuery(condition, type); } @Override - public void registerRecordComponents(ConditionalElement> type) { - reflectionSupport.registerAllRecordComponentsQuery(type.getCondition(), type.getElement()); + public void registerRecordComponents(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllRecordComponentsQuery(condition, type); } @Override - public void registerPermittedSubclasses(ConditionalElement> type) { - reflectionSupport.registerAllPermittedSubclassesQuery(type.getCondition(), type.getElement()); + public void registerPermittedSubclasses(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllPermittedSubclassesQuery(condition, type); } @Override - public void registerNestMembers(ConditionalElement> type) { - reflectionSupport.registerAllNestMembersQuery(type.getCondition(), type.getElement()); + public void registerNestMembers(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllNestMembersQuery(condition, type); } @Override - public void registerSigners(ConditionalElement> type) { - reflectionSupport.registerAllSignersQuery(type.getCondition(), type.getElement()); + public void registerSigners(ConfigurationCondition condition, Class type) { + reflectionSupport.registerAllSignersQuery(condition, type); } @Override - public void registerPublicFields(ConditionalElement> type) { - reflectionSupport.registerAllFieldsQuery(type.getCondition(), type.getElement()); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllFieldsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredFields(ConditionalElement> type) { - reflectionSupport.registerAllDeclaredFieldsQuery(type.getCondition(), type.getElement()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + ((ReflectionDataBuilder) reflectionSupport).registerAllDeclaredFieldsQuery(condition, queriedOnly, type); } @Override - public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllMethodsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllMethodsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllDeclaredMethodsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllDeclaredMethodsQuery(condition, queriedOnly, type); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllConstructorsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllConstructorsQuery(condition, queriedOnly, type); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) { - reflectionSupport.registerAllDeclaredConstructorsQuery(type.getCondition(), queriedOnly, type.getElement()); + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + reflectionSupport.registerAllDeclaredConstructorsQuery(condition, queriedOnly, type); } @Override - public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException { + public void registerField(ConfigurationCondition condition, Class type, String fieldName, boolean allowWrite) throws NoSuchFieldException { try { - super.registerField(type, fieldName, allowWrite); + super.registerField(condition, type, fieldName, allowWrite); } catch (NoSuchFieldException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerFieldLookup(type.getCondition(), type.getElement(), fieldName); + reflectionSupport.registerFieldLookup(condition, type, fieldName); } else { throw e; } @@ -131,12 +132,12 @@ public void registerField(ConditionalElement> type, String fieldName, b } @Override - public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { try { - super.registerMethod(queriedOnly, type, methodName, methodParameterTypes); + super.registerMethod(condition, queriedOnly, type, methodName, methodParameterTypes); } catch (NoSuchMethodException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerMethodLookup(type.getCondition(), type.getElement(), methodName, getParameterTypes(methodParameterTypes)); + reflectionSupport.registerMethodLookup(condition, type, methodName, getParameterTypes(methodParameterTypes)); } else { throw e; } @@ -144,12 +145,12 @@ public void registerMethod(boolean queriedOnly, ConditionalElement> typ } @Override - public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, Class type, List> methodParameterTypes) throws NoSuchMethodException { try { - super.registerConstructor(queriedOnly, type, methodParameterTypes); + super.registerConstructor(condition, queriedOnly, type, methodParameterTypes); } catch (NoSuchMethodException e) { if (throwMissingRegistrationErrors()) { - reflectionSupport.registerConstructorLookup(type.getCondition(), type.getElement(), getParameterTypes(methodParameterTypes)); + reflectionSupport.registerConstructorLookup(condition, type, getParameterTypes(methodParameterTypes)); } else { throw e; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java index 1725a1cc7752..73a12c2d8384 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/config/RegistryAdapter.java @@ -27,21 +27,25 @@ import java.lang.reflect.Executable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.List; +import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.ReflectionRegistry; import org.graalvm.nativeimage.impl.RuntimeReflectionSupport; import com.oracle.svm.core.TypeResult; -import com.oracle.svm.core.configure.ConditionalElement; +import com.oracle.svm.core.configure.ConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.NamedConfigurationTypeDescriptor; +import com.oracle.svm.core.configure.ProxyConfigurationTypeDescriptor; import com.oracle.svm.core.configure.ReflectionConfigurationParserDelegate; +import com.oracle.svm.core.jdk.proxy.DynamicProxyRegistry; +import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.util.ClassUtil; -import jdk.vm.ci.meta.MetaUtil; - -public class RegistryAdapter implements ReflectionConfigurationParserDelegate>> { +public class RegistryAdapter implements ReflectionConfigurationParserDelegate> { private final ReflectionRegistry registry; private final ImageClassLoader classLoader; @@ -59,102 +63,116 @@ public static RegistryAdapter create(ReflectionRegistry registry, ImageClassLoad } @Override - public void registerType(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement()); - } - - @Override - public TypeResult resolveCondition(String typeName) { - String canonicalizedName = canonicalizeTypeName(typeName); - TypeResult> clazz = classLoader.findClass(canonicalizedName); - return clazz.map(Class::getTypeName) - .map(ConfigurationCondition::create); + public void registerType(ConfigurationCondition condition, Class type) { + registry.register(condition, type); } @Override - public TypeResult>> resolveType(ConfigurationCondition condition, String typeName, boolean allowPrimitives) { - String name = canonicalizeTypeName(typeName); - TypeResult> clazz = classLoader.findClass(name, allowPrimitives); - return clazz.map(c -> new ConditionalElement<>(condition, c)); + public TypeResult> resolveType(ConfigurationCondition condition, ConfigurationTypeDescriptor typeDescriptor, boolean allowPrimitives) { + switch (typeDescriptor.getDescriptorType()) { + case NAMED -> { + NamedConfigurationTypeDescriptor namedDescriptor = (NamedConfigurationTypeDescriptor) typeDescriptor; + return classLoader.findClass(namedDescriptor.name(), allowPrimitives); + } + case PROXY -> { + return resolveProxyType((ProxyConfigurationTypeDescriptor) typeDescriptor); + } + default -> { + throw VMError.shouldNotReachHere("Unknown type descriptor kind: %s", typeDescriptor.getDescriptorType()); + } + } } - private static String canonicalizeTypeName(String typeName) { - String name = typeName; - if (name.indexOf('[') != -1) { - /* accept "int[][]", "java.lang.String[]" */ - name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + private TypeResult> resolveProxyType(ProxyConfigurationTypeDescriptor typeDescriptor) { + String typeName = typeDescriptor.toString(); + List>> interfaceResults = typeDescriptor.interfaceNames().stream().map(name -> { + NamedConfigurationTypeDescriptor typeDescriptor1 = new NamedConfigurationTypeDescriptor(name); + return classLoader.findClass(typeDescriptor1.name(), false); + }).toList(); + List> interfaces = new ArrayList<>(); + for (TypeResult> intf : interfaceResults) { + if (!intf.isPresent()) { + return TypeResult.forException(typeName, intf.getException()); + } + interfaces.add(intf.get()); + } + try { + DynamicProxyRegistry proxyRegistry = ImageSingletons.lookup(DynamicProxyRegistry.class); + Class proxyClass = proxyRegistry.getProxyClassHosted(interfaces.toArray(Class[]::new)); + return TypeResult.forType(typeName, proxyClass); + } catch (Throwable t) { + return TypeResult.forException(typeName, t); } - return name; } @Override - public void registerPublicClasses(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getClasses()); + public void registerPublicClasses(ConfigurationCondition condition, Class type) { + registry.register(condition, type.getClasses()); } @Override - public void registerDeclaredClasses(ConditionalElement> type) { - registry.register(type.getCondition(), type.getElement().getDeclaredClasses()); + public void registerDeclaredClasses(ConfigurationCondition condition, Class type) { + registry.register(condition, type.getDeclaredClasses()); } @Override - public void registerRecordComponents(ConditionalElement> type) { + public void registerRecordComponents(ConfigurationCondition condition, Class type) { } @Override - public void registerPermittedSubclasses(ConditionalElement> type) { + public void registerPermittedSubclasses(ConfigurationCondition condition, Class type) { } @Override - public void registerNestMembers(ConditionalElement> type) { + public void registerNestMembers(ConfigurationCondition condition, Class type) { } @Override - public void registerSigners(ConditionalElement> type) { + public void registerSigners(ConfigurationCondition condition, Class type) { } @Override - public void registerPublicFields(ConditionalElement> type) { - registry.register(type.getCondition(), false, type.getElement().getFields()); + public void registerPublicFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, false, type.getFields()); } @Override - public void registerDeclaredFields(ConditionalElement> type) { - registry.register(type.getCondition(), false, type.getElement().getDeclaredFields()); + public void registerDeclaredFields(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, false, type.getDeclaredFields()); } @Override - public void registerPublicMethods(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getMethods()); + public void registerPublicMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getMethods()); } @Override - public void registerDeclaredMethods(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredMethods()); + public void registerDeclaredMethods(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getDeclaredMethods()); } @Override - public void registerPublicConstructors(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getConstructors()); + public void registerPublicConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getConstructors()); } @Override - public void registerDeclaredConstructors(boolean queriedOnly, ConditionalElement> type) { - registry.register(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructors()); + public void registerDeclaredConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + registry.register(condition, queriedOnly, type.getDeclaredConstructors()); } @Override - public void registerField(ConditionalElement> type, String fieldName, boolean allowWrite) throws NoSuchFieldException { - registry.register(type.getCondition(), allowWrite, type.getElement().getDeclaredField(fieldName)); + public void registerField(ConfigurationCondition condition, Class type, String fieldName, boolean allowWrite) throws NoSuchFieldException { + registry.register(condition, allowWrite, type.getDeclaredField(fieldName)); } @Override - public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElement> type, String methodName) { + public boolean registerAllMethodsWithName(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName) { boolean found = false; - Executable[] methods = type.getElement().getDeclaredMethods(); + Executable[] methods = type.getDeclaredMethods(); for (Executable method : methods) { if (method.getName().equals(methodName)) { - registerExecutable(type.getCondition(), queriedOnly, method); + registerExecutable(condition, queriedOnly, method); found = true; } } @@ -162,17 +180,16 @@ public boolean registerAllMethodsWithName(boolean queriedOnly, ConditionalElemen } @Override - public boolean registerAllConstructors(boolean queriedOnly, ConditionalElement> type) { - Executable[] methods = type.getElement().getDeclaredConstructors(); - registerExecutable(type.getCondition(), queriedOnly, methods); + public boolean registerAllConstructors(ConfigurationCondition condition, boolean queriedOnly, Class type) { + Executable[] methods = type.getDeclaredConstructors(); + registerExecutable(condition, queriedOnly, methods); return methods.length > 0; } @Override - public void registerUnsafeAllocated(ConditionalElement> clazz) { - Class type = clazz.getElement(); + public void registerUnsafeAllocated(ConfigurationCondition condition, Class type) { if (!type.isArray() && !type.isInterface() && !Modifier.isAbstract(type.getModifiers())) { - registry.register(clazz.getCondition(), true, clazz.getElement()); + registry.register(condition, true, type); /* * Ignore otherwise as the implementation of allocateInstance will anyhow throw an * exception. @@ -181,11 +198,11 @@ public void registerUnsafeAllocated(ConditionalElement> clazz) { } @Override - public void registerMethod(boolean queriedOnly, ConditionalElement> type, String methodName, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerMethod(ConfigurationCondition condition, boolean queriedOnly, Class type, String methodName, List> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); Method method; try { - method = type.getElement().getDeclaredMethod(methodName, parameterTypesArray); + method = type.getDeclaredMethod(methodName, parameterTypesArray); } catch (NoClassDefFoundError e) { /* * getDeclaredMethod() builds a set of all the declared methods, which can fail when a @@ -196,24 +213,22 @@ public void registerMethod(boolean queriedOnly, ConditionalElement> typ * precisely because the application used getMethod() instead of getDeclaredMethod(). */ try { - method = type.getElement().getMethod(methodName, parameterTypesArray); + method = type.getMethod(methodName, parameterTypesArray); } catch (Throwable ignored) { throw e; } } - registerExecutable(type.getCondition(), queriedOnly, method); + registerExecutable(condition, queriedOnly, method); } @Override - public void registerConstructor(boolean queriedOnly, ConditionalElement> type, List>> methodParameterTypes) throws NoSuchMethodException { + public void registerConstructor(ConfigurationCondition condition, boolean queriedOnly, Class type, List> methodParameterTypes) throws NoSuchMethodException { Class[] parameterTypesArray = getParameterTypes(methodParameterTypes); - registerExecutable(type.getCondition(), queriedOnly, type.getElement().getDeclaredConstructor(parameterTypesArray)); + registerExecutable(condition, queriedOnly, type.getDeclaredConstructor(parameterTypesArray)); } - static Class[] getParameterTypes(List>> methodParameterTypes) { - return methodParameterTypes.stream() - .map(ConditionalElement::getElement) - .toArray(Class[]::new); + static Class[] getParameterTypes(List> methodParameterTypes) { + return methodParameterTypes.toArray(Class[]::new); } private void registerExecutable(ConfigurationCondition condition, boolean queriedOnly, Executable... executable) { @@ -221,12 +236,12 @@ private void registerExecutable(ConfigurationCondition condition, boolean querie } @Override - public String getTypeName(ConditionalElement> type) { - return type.getElement().getTypeName(); + public String getTypeName(Class type) { + return type.getTypeName(); } @Override - public String getSimpleName(ConditionalElement> type) { - return ClassUtil.getUnqualifiedName(type.getElement()); + public String getSimpleName(Class type) { + return ClassUtil.getUnqualifiedName(type); } } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java index 2f1dfeb40bf1..3df99310714c 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/jni/JNIAccessFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.jni; +import static com.oracle.svm.core.configure.ConfigurationParser.JNI_KEY; + import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; @@ -60,7 +62,6 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.graal.pointsto.meta.AnalysisUniverse; import com.oracle.svm.core.config.ObjectLayout; -import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -191,9 +192,13 @@ public void afterRegistration(AfterRegistrationAccess arg) { runtimeSupport = new JNIRuntimeAccessibilitySupportImpl(); ImageSingletons.add(RuntimeJNIAccessSupport.class, runtimeSupport); - ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(runtimeSupport, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "JNI", - ConfigurationFiles.Options.JNIConfigurationFiles, ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(JNI_KEY, true, runtimeSupport, + access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "JNI"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, runtimeSupport, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "JNI", ConfigurationFiles.Options.JNIConfigurationFiles, + ConfigurationFiles.Options.JNIConfigurationResources, ConfigurationFile.JNI.getFileName()); } private class JNIRuntimeAccessibilitySupportImpl extends ConditionalConfigurationRegistry @@ -209,7 +214,9 @@ public void register(ConfigurationCondition condition, boolean unsafeAllocated, @Override public void register(ConfigurationCondition condition, boolean queriedOnly, Executable... methods) { abortIfSealed(); - registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); + if (!queriedOnly) { + registerConditionalConfiguration(condition, () -> newMethods.addAll(Arrays.asList(methods))); + } } @Override diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java index 64283d256c84..63a93b6a69f0 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionDataBuilder.java @@ -410,26 +410,30 @@ public void registerConstructorLookup(ConfigurationCondition condition, Class @Override public void register(ConfigurationCondition condition, boolean finalIsWritable, Field... fields) { checkNotSealed(); - registerInternal(condition, fields); + registerInternal(condition, false, fields); } - private void registerInternal(ConfigurationCondition condition, Field... fields) { + private void registerInternal(ConfigurationCondition condition, boolean queriedOnly, Field... fields) { register(analysisUniverse -> registerConditionalConfiguration(condition, () -> { for (Field field : fields) { - analysisUniverse.getBigbang().postTask(debug -> registerField(field)); + analysisUniverse.getBigbang().postTask(debug -> registerField(queriedOnly, field)); } })); } @Override public void registerAllFieldsQuery(ConfigurationCondition condition, Class clazz) { + registerAllFieldsQuery(condition, false, clazz); + } + + public void registerAllFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { checkNotSealed(); for (Class current = clazz; current != null; current = current.getSuperclass()) { final Class currentLambda = current; registerConditionalConfiguration(condition, () -> setQueryFlag(currentLambda, ALL_FIELDS_FLAG)); } try { - registerInternal(condition, clazz.getFields()); + registerInternal(condition, queriedOnly, clazz.getFields()); } catch (LinkageError e) { /* Ignore the error */ } @@ -437,23 +441,27 @@ public void registerAllFieldsQuery(ConfigurationCondition condition, Class cl @Override public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, Class clazz) { + registerAllDeclaredFieldsQuery(condition, false, clazz); + } + + public void registerAllDeclaredFieldsQuery(ConfigurationCondition condition, boolean queriedOnly, Class clazz) { checkNotSealed(); registerConditionalConfiguration(condition, () -> setQueryFlag(clazz, ALL_DECLARED_FIELDS_FLAG)); try { - registerInternal(condition, clazz.getDeclaredFields()); + registerInternal(condition, queriedOnly, clazz.getDeclaredFields()); } catch (LinkageError e) { /* Ignore the error */ } } - private void registerField(Field reflectField) { + private void registerField(boolean queriedOnly, Field reflectField) { if (SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { return; } AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); if (registeredFields.put(analysisField, reflectField) == null) { - registerTypesForField(analysisField, reflectField); + registerTypesForField(analysisField, reflectField, true); AnalysisType declaringClass = analysisField.getDeclaringClass(); /* @@ -468,13 +476,21 @@ private void registerField(Field reflectField) { processAnnotationField(reflectField); } } + + /* + * We need to run this even if the method has already been registered, in case it was only + * registered as queried. + */ + if (!queriedOnly) { + registerTypesForField(analysisField, reflectField, false); + } } @Override public void registerFieldLookup(ConfigurationCondition condition, Class declaringClass, String fieldName) { checkNotSealed(); try { - registerInternal(condition, declaringClass.getDeclaredField(fieldName)); + registerInternal(condition, false, declaringClass.getDeclaredField(fieldName)); } catch (NoSuchFieldException e) { registerConditionalConfiguration(condition, () -> negativeFieldLookups.computeIfAbsent(metaAccess.lookupJavaType(declaringClass), (key) -> ConcurrentHashMap.newKeySet()).add(fieldName)); } @@ -641,13 +657,15 @@ private Object[] getEnclosingMethodInfo(Class clazz) { } } - private void registerTypesForField(AnalysisField analysisField, Field reflectField) { - /* - * Reflection accessors use Unsafe, so ensure that all reflectively accessible fields are - * registered as unsafe-accessible, whether they have been explicitly registered or their - * Field object is reachable in the image heap. - */ - analysisField.registerAsUnsafeAccessed("is registered for reflection."); + private void registerTypesForField(AnalysisField analysisField, Field reflectField, boolean queriedOnly) { + if (!queriedOnly) { + /* + * Reflection accessors use Unsafe, so ensure that all reflectively accessible fields + * are registered as unsafe-accessible, whether they have been explicitly registered or + * their Field object is reachable in the image heap. + */ + analysisField.registerAsUnsafeAccessed("is registered for reflection."); + } /* * The generic signature is parsed at run time, so we need to make all the types necessary @@ -992,7 +1010,7 @@ public void registerHeapReflectionField(Field reflectField, ScanReason reason) { assert !sealed; AnalysisField analysisField = metaAccess.lookupJavaField(reflectField); if (heapFields.put(analysisField, reflectField) == null && !SubstitutionReflectivityFilter.shouldExclude(reflectField, metaAccess, universe)) { - registerTypesForField(analysisField, reflectField); + registerTypesForField(analysisField, reflectField, false); if (analysisField.getDeclaringClass().isAnnotation()) { processAnnotationField(reflectField); } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java index 372d1e533162..ddf7d4ce88e9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/ReflectionFeature.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.hosted.reflect; +import static com.oracle.svm.core.configure.ConfigurationParser.REFLECTION_KEY; + import java.lang.invoke.MethodHandle; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; @@ -52,7 +54,6 @@ import com.oracle.svm.core.ParsingReason; import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.annotate.Delete; -import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ReflectionConfigurationParser; @@ -261,10 +262,13 @@ public void duringSetup(DuringSetupAccess a) { aUniverse = access.getUniverse(); reflectionData.duringSetup(access.getMetaAccess(), aUniverse); - ReflectionConfigurationParser>> parser = ConfigurationParserUtils.create(reflectionData, access.getImageClassLoader()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "reflection", - ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, - ConfigurationFile.REFLECTION.getFileName()); + ReflectionConfigurationParser> parser = ConfigurationParserUtils.create(REFLECTION_KEY, true, reflectionData, + access.getImageClassLoader()); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, access.getImageClassLoader(), "reflection"); + ReflectionConfigurationParser> legacyParser = ConfigurationParserUtils.create(null, false, reflectionData, + access.getImageClassLoader()); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, access.getImageClassLoader(), "reflection", + ConfigurationFiles.Options.ReflectionConfigurationFiles, ConfigurationFiles.Options.ReflectionConfigurationResources, ConfigurationFile.REFLECTION.getFileName()); loader = access.getImageClassLoader(); annotationSubstitutions = ((Inflation) access.getBigBang()).getAnnotationSubstitutionProcessor(); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java index 3caa69ae79f8..f311da09d87d 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/DynamicProxyFeature.java @@ -32,6 +32,7 @@ import org.graalvm.nativeimage.hosted.Feature; import org.graalvm.nativeimage.impl.RuntimeProxyCreationSupport; +import com.oracle.svm.core.configure.ConditionalElement; import com.oracle.svm.core.configure.ConfigurationFile; import com.oracle.svm.core.configure.ConfigurationFiles; import com.oracle.svm.core.configure.ProxyConfigurationParser; @@ -68,7 +69,8 @@ public void duringSetup(DuringSetupAccess a) { ConfigurationTypeResolver typeResolver = new ConfigurationTypeResolver("resource configuration", imageClassLoader); ProxyRegistry proxyRegistry = new ProxyRegistry(typeResolver, dynamicProxySupport, imageClassLoader); ImageSingletons.add(ProxyRegistry.class, proxyRegistry); - ProxyConfigurationParser parser = new ProxyConfigurationParser(proxyRegistry, ConfigurationFiles.Options.StrictConfiguration.getValue()); + ProxyConfigurationParser parser = new ProxyConfigurationParser(ConfigurationFiles.Options.StrictConfiguration.getValue(), + (cond, intfs) -> proxyRegistry.accept(new ConditionalElement<>(cond, intfs))); loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "dynamic proxy", ConfigurationFiles.Options.DynamicProxyConfigurationFiles, ConfigurationFiles.Options.DynamicProxyConfigurationResources, ConfigurationFile.DYNAMIC_PROXY.getFileName()); diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java index 82794c408f11..086bb379b188 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/proxy/ProxyRegistry.java @@ -59,7 +59,7 @@ public void accept(ConditionalElement> proxies) { public Class createProxyClassForSerialization(ConditionalElement> proxies) { Class[] interfaces = checkIfInterfacesAreValid(proxies); if (interfaces != null) { - return dynamicProxySupport.createProxyClassForSerialization(interfaces); + return dynamicProxySupport.getProxyClassHosted(interfaces); } return null; diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java index c665eeff3124..dd4253deb42a 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/reflect/serialize/SerializationFeature.java @@ -122,14 +122,23 @@ public void duringSetup(DuringSetupAccess a) { SerializationDenyRegistry serializationDenyRegistry = new SerializationDenyRegistry(typeResolver); serializationBuilder = new SerializationBuilder(serializationDenyRegistry, access, typeResolver, ImageSingletons.lookup(ProxyRegistry.class)); ImageSingletons.add(RuntimeSerializationSupport.class, serializationBuilder); - SerializationConfigurationParser denyCollectorParser = new SerializationConfigurationParser(serializationDenyRegistry, ConfigurationFiles.Options.StrictConfiguration.getValue()); + + Boolean strictConfiguration = ConfigurationFiles.Options.StrictConfiguration.getValue(); + + SerializationConfigurationParser parser = SerializationConfigurationParser.create(true, serializationBuilder, + strictConfiguration); + loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurationsFromCombinedFile(parser, imageClassLoader, "serialization"); + + SerializationConfigurationParser denyCollectorParser = SerializationConfigurationParser.create(false, serializationDenyRegistry, + strictConfiguration); ConfigurationParserUtils.parseAndRegisterConfigurations(denyCollectorParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationDenyConfigurationFiles, ConfigurationFiles.Options.SerializationDenyConfigurationResources, ConfigurationFile.SERIALIZATION_DENY.getFileName()); - SerializationConfigurationParser parser = new SerializationConfigurationParser(serializationBuilder, ConfigurationFiles.Options.StrictConfiguration.getValue()); - loadedConfigurations = ConfigurationParserUtils.parseAndRegisterConfigurations(parser, imageClassLoader, "serialization", + SerializationConfigurationParser legacyParser = SerializationConfigurationParser.create(false, serializationBuilder, + strictConfiguration); + loadedConfigurations += ConfigurationParserUtils.parseAndRegisterConfigurations(legacyParser, imageClassLoader, "serialization", ConfigurationFiles.Options.SerializationConfigurationFiles, ConfigurationFiles.Options.SerializationConfigurationResources, ConfigurationFile.SERIALIZATION.getFileName());