From b7b2fcd6d7b166885564e5b03d63454fd29b3f0c Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 6 Aug 2026 17:45:38 +0200 Subject: [PATCH 1/8] Use clean --aggressive in bisect benchmark --- mx.graalpython/mx_graalpython_bisect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mx.graalpython/mx_graalpython_bisect.py b/mx.graalpython/mx_graalpython_bisect.py index c696c806cc..8893ac14b7 100644 --- a/mx.graalpython/mx_graalpython_bisect.py +++ b/mx.graalpython/mx_graalpython_bisect.py @@ -343,7 +343,7 @@ def checkout_and_build(repo_path, commit): build_command = shlex.split(args.build_command) if not args.no_clean: try: - clean_command = build_command[:build_command.index('build')] + ['clean'] + clean_command = build_command[:build_command.index('build')] + ['clean', '--all', '--aggressive'] retcode = mx.run(clean_command, nonZeroIsFatal=False) if retcode: print("Warning: clean command failed") From 90806c4335b0ce5d63c401cceefdf5728479f6aa Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 6 Aug 2026 18:02:24 +0200 Subject: [PATCH 2/8] Make it possible to bisect pyperformance --- .../rota-bench-regression-analysis/SKILL.md | 1 + scripts/bisect_benchmark_regression.py | 36 ++++++++++++------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.agents/skills/rota-bench-regression-analysis/SKILL.md b/.agents/skills/rota-bench-regression-analysis/SKILL.md index ee5449ace3..5779ea32dc 100644 --- a/.agents/skills/rota-bench-regression-analysis/SKILL.md +++ b/.agents/skills/rota-bench-regression-analysis/SKILL.md @@ -78,6 +78,7 @@ git diff --stat GOOD..BAD - In the attributed section, use this header format: `abcd1234efgh | author@oracle.com | Full subject` - Unattributed changes that look plausible go to "to bisect", flaky ones go to "to watch" - In the "to bisect" section, add an invocation (don't execute yet) of `scripts/bisect_benchmark_regression.py` that can bisect it (use unabbreviated commits in this case) +- The positional benchmark name identifies the result to compare. If the harness runs that result through a differently named parent or group, pass the runnable name with `--benchmark-selector`. For example, use result `pyperformance-suite.scimark_fft` with `--benchmark-selector scimark`, because pyperformance runs the `scimark` group and reports `scimark_fft` separately. - In the "to watch" section, say whether the item looks flaky, or likely the same cause as another attributed item. - Do not abbreviate commit subjects. - Keep author emails. diff --git a/scripts/bisect_benchmark_regression.py b/scripts/bisect_benchmark_regression.py index 1207b5adbe..174ba8b29c 100644 --- a/scripts/bisect_benchmark_regression.py +++ b/scripts/bisect_benchmark_regression.py @@ -105,10 +105,17 @@ def parse_args() -> argparse.Namespace: description="Generate and optionally submit a bisect-benchmark CI workflow for a benchmark regression.", ) parser.add_argument("benchmark_job_name", help="Benchmark job key, for example pybench-micro-graalvm_ee_default-post_merge-linux-amd64-jdk-latest.") - parser.add_argument("benchmark_name", help="Benchmark selector to narrow the benchmark command to a single benchmark.") + parser.add_argument("benchmark_name", help="Benchmark result name to bisect.") parser.add_argument("metric", help="Benchmark metric name, or WORKS.") parser.add_argument("good_commit", help="Known good GraalPy commit or ref.") parser.add_argument("bad_commit", help="Known bad GraalPy commit or ref.") + parser.add_argument( + "--benchmark-selector", + help=( + "Benchmark selector used to narrow the benchmark command. Defaults to benchmark_name. " + "Use this when a harness selector produces several separately named results." + ), + ) parser.add_argument("--config-only", action="store_true", help="Print the generated bisect config and exit.") parser.add_argument("--force-rebuild", action="store_true", help="Submit a fresh bisect job even if one already exists.") parser.add_argument("--debug", action="store_true", help="Print progress information to stderr.") @@ -153,16 +160,19 @@ def abbreviate_commit(commit: str) -> str: return commit[:12] -def build_branch_name(job_name: str, benchmark_name: str, metric: str, good_commit: str, bad_commit: str) -> str: - slug = "_".join( - [ - job_name, - benchmark_name, - metric, - abbreviate_commit(good_commit), - abbreviate_commit(bad_commit), - ] - ) +def build_branch_name( + job_name: str, + benchmark_name: str, + metric: str, + good_commit: str, + bad_commit: str, + benchmark_selector: str | None = None, +) -> str: + parts = [job_name, benchmark_name] + if benchmark_selector: + parts.append(benchmark_selector) + parts.extend([metric, abbreviate_commit(good_commit), abbreviate_commit(bad_commit)]) + slug = "_".join(parts) return "bisect/{}".format(slug) @@ -509,6 +519,7 @@ def generate_config( repo_dir: Path, benchmark_job_name: str, benchmark_name: str, + benchmark_selector: str | None, metric: str, good_commit: str, bad_commit: str, @@ -516,7 +527,7 @@ def generate_config( reference_build = get_reference_build(repo_dir, benchmark_job_name, bad_commit, good_commit) debug("Using reference build {} ({})".format(reference_build.build_number, reference_build.url)) build_log = run_command(["gdev-cli", "buildbot", "get-log", str(reference_build.build_number)], cwd=repo_dir) - build_command, benchmark_command = extract_commands(build_log, benchmark_name) + build_command, benchmark_command = extract_commands(build_log, benchmark_selector or benchmark_name) results_benchmark_name = None if metric == "WORKS" else benchmark_selector_for_command(benchmark_name) enterprise = "enterprise" in build_command return build_config_text( @@ -543,6 +554,7 @@ def main() -> int: repo_dir=repo_dir, benchmark_job_name=args.benchmark_job_name, benchmark_name=args.benchmark_name, + benchmark_selector=args.benchmark_selector, metric=args.metric, good_commit=good_commit, bad_commit=bad_commit, From 6cc4286350aa284c8e8569860f5ee435f3c5147f Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 6 Aug 2026 18:02:48 +0200 Subject: [PATCH 3/8] Make the --force arg in bisect script repush new branch --- scripts/bisect_benchmark_regression.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/bisect_benchmark_regression.py b/scripts/bisect_benchmark_regression.py index 174ba8b29c..de28a01917 100644 --- a/scripts/bisect_benchmark_regression.py +++ b/scripts/bisect_benchmark_regression.py @@ -497,7 +497,7 @@ def write_temp_branch(repo_dir: Path, branch_name: str, config_text: str) -> str run_command(["git", "add", *[str(path) for path in BRANCH_SUPPORT_FILES]], cwd=clone_dir) run_command(["git", "commit", "-m", commit_message], cwd=clone_dir) commit = resolve_commit(clone_dir, "HEAD") - run_command(["git", "push", "origin", "HEAD:refs/heads/{}".format(branch_name)], cwd=clone_dir) + run_command(["git", "push", "--force", "origin", "HEAD:refs/heads/{}".format(branch_name)], cwd=clone_dir) debug("Pushed branch {} at {}".format(branch_name, commit)) return commit @@ -570,11 +570,12 @@ def main() -> int: args.metric, good_commit, bad_commit, + args.benchmark_selector, ) debug("Branch name: {}".format(branch_name)) branch_head = get_remote_branch_head(repo_dir, branch_name) - if branch_head is None: + if branch_head is None or args.force_rebuild: branch_head = write_temp_branch(repo_dir, branch_name, config_text) wait_for_enumeration( repo_dir, @@ -589,7 +590,7 @@ def main() -> int: else: debug("Remote branch head: {}".format(branch_head)) existing_builds = get_matching_builds(repo_dir, branch_head, BISECT_JOB_NAME) - if existing_builds and not args.force_rebuild: + if existing_builds: build = wait_for_bisect_build(repo_dir, branch_head) else: wait_for_enumeration( From 061b0bd508d67531bb822abbaa45b6445dcce651 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Thu, 6 Aug 2026 18:15:30 +0200 Subject: [PATCH 4/8] Fetch the right JDK for coverage jobs --- ci/python-gate.libsonnet | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/python-gate.libsonnet b/ci/python-gate.libsonnet index f7ab83145c..14e8870953 100644 --- a/ci/python-gate.libsonnet +++ b/ci/python-gate.libsonnet @@ -517,6 +517,8 @@ $.overlay_imports.BUILDBOT_COMMIT_SERVICE + '?repoName=graal&target=weekly&before-ts=${MAIN_COMMIT_TS}']], ["git", "clone", $.overlay_imports.GRAAL_ENTERPRISE_GIT, "../graal-enterprise"], ['git', '-C', '../graal', 'checkout', '${GRAAL_COMMIT}'], + ['mx', '-p', '../graal', 'fetch-jdk', '-A', 'labsjdk-ce-latest'], + ['set-export', 'JAVA_HOME', ['mx', '-p', '../graal', 'get-jdk-path', 'labsjdk-ce-latest']], // NOTE: this will checkout older graalpy. We need to live with that to ensure consistency with graal ['mx', '-p', '../graal/vm', '--dynamicimports', 'graalpython', 'sforceimports'], // NOTE: jvm-only, so not need to handle substratevm-enterprise-gcs From 34ca4b4f7e4f2975ed9c4d175b5479eb3c93d135 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 7 Aug 2026 00:07:57 +0200 Subject: [PATCH 5/8] Fix keeping the right graal in bisect script --- mx.graalpython/mx_graalpython_bisect.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mx.graalpython/mx_graalpython_bisect.py b/mx.graalpython/mx_graalpython_bisect.py index 8893ac14b7..f42f5f8213 100644 --- a/mx.graalpython/mx_graalpython_bisect.py +++ b/mx.graalpython/mx_graalpython_bisect.py @@ -305,10 +305,15 @@ def checkout(repo_path: Path, commit): if repo_path == DIR: mx.run_mx(['sforceimports'], suite=str(DIR)) if args.enterprise: + # Keep the Graal revision selected by the current bisection point. The + # enterprise suite imports Graal, so its sforceimports would otherwise + # replace that revision with the one recorded in graal-enterprise. + graal_commit = get_commit(GRAAL_DIR) if repo_path.name != 'graal-enterprise': mx.run_mx(['--quiet', 'checkout-downstream', 'vm', 'vm-enterprise', '--no-fetch'], suite=str(VM_ENTERPRISE_DIR)) mx.run_mx(['--dy', 'substratevm-enterprise-gcs', 'sforceimports'], suite=str(VM_ENTERPRISE_DIR)) + GIT.update_to_branch(GRAAL_DIR, graal_commit) debug_str = f"debug: {SUITE.name}={get_commit(SUITE.vc_dir)} graal={get_commit(GRAAL_DIR)}" if args.enterprise: debug_str += f" graal-enterprise={get_commit(GRAAL_ENTERPRISE_DIR)}" From 68e17d897da8b51b0ce9cf7797c923e4ebefa8cc Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Fri, 7 Aug 2026 13:05:32 +0200 Subject: [PATCH 6/8] Remove pyo3 test workaround --- mx.graalpython/downstream_tests.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/mx.graalpython/downstream_tests.py b/mx.graalpython/downstream_tests.py index e4948235b3..fe9982c230 100644 --- a/mx.graalpython/downstream_tests.py +++ b/mx.graalpython/downstream_tests.py @@ -117,9 +117,6 @@ def downstream_test_virtualenv(graalpy, testdir): def downstream_test_pyo3(graalpy, testdir): run(['git', 'clone', 'https://github.com/PyO3/pyo3.git', '-b', 'main', '--depth', '1'], cwd=testdir) src = testdir / 'pyo3' - # The runtime test session does not run mypy. Avoid installing it because its - # librt dependency relies on CPython-internal APIs that are incompatible with GraalPy. - replace_in_file(src / 'pytests/pyproject.toml', r'^\s*"mypy[^\n]*\n', '', flags=re.MULTILINE) venv = src / 'venv' run([graalpy, '-m', 'venv', str(venv)]) run_in_venv(venv, ['python', '-m', 'pip', 'install', '--upgrade', 'pip', 'nox[uv]']) From 433254ae95c856f18af1f76b9b1ae5e1b40cdec6 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Wed, 5 Aug 2026 16:09:53 +0200 Subject: [PATCH 7/8] Implement marshal allow_code parameter --- .../src/tests/unittest_tags/test_marshal.txt | 1 + .../modules/MarshalModuleBuiltins.java | 97 ++++++++++++++----- .../graal/python/nodes/ErrorMessages.java | 2 + 3 files changed, 74 insertions(+), 26 deletions(-) diff --git a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_marshal.txt b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_marshal.txt index f68eaa6e60..b1396a9f3a 100644 --- a/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_marshal.txt +++ b/graalpython/com.oracle.graal.python.test/src/tests/unittest_tags/test_marshal.txt @@ -18,6 +18,7 @@ test.test_marshal.BugsTestCase.test_version_argument @ darwin-arm64,linux-aarch6 !test.test_marshal.CodeTestCase.test_code test.test_marshal.CodeTestCase.test_different_filenames @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_marshal.CodeTestCase.test_many_codeobjects @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github +test.test_marshal.CodeTestCase.test_no_allow_code @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_marshal.CompatibilityTestCase.test0To3 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_marshal.CompatibilityTestCase.test1To3 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github test.test_marshal.CompatibilityTestCase.test2To3 @ darwin-arm64,linux-aarch64,linux-aarch64-github,linux-x86_64,linux-x86_64-github,win32-AMD64,win32-AMD64-github diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MarshalModuleBuiltins.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MarshalModuleBuiltins.java index 5e3a8c62f3..70af888874 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MarshalModuleBuiltins.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/builtins/modules/MarshalModuleBuiltins.java @@ -119,10 +119,9 @@ import com.oracle.graal.python.nodes.bytecode_dsl.PBytecodeDSLRootNodeGen; import com.oracle.graal.python.nodes.call.special.LookupAndCallBinaryNode; import com.oracle.graal.python.nodes.function.PythonBuiltinBaseNode; -import com.oracle.graal.python.nodes.function.PythonBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonBinaryClinicBuiltinNode; +import com.oracle.graal.python.nodes.function.builtins.PythonQuaternaryClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.PythonTernaryClinicBuiltinNode; -import com.oracle.graal.python.nodes.function.builtins.PythonUnaryClinicBuiltinNode; import com.oracle.graal.python.nodes.function.builtins.clinic.ArgumentClinicProvider; import com.oracle.graal.python.runtime.ExecutionContext.BoundaryCallContext; import com.oracle.graal.python.runtime.IndirectCallData.BoundaryCallData; @@ -174,17 +173,18 @@ public void initialize(Python3Core core) { addBuiltinConstant(T_VERSION, CURRENT_VERSION); } - @Builtin(name = "dump", minNumOfPositionalArgs = 2, parameterNames = {"value", "file", "version"}) + @Builtin(name = "dump", minNumOfPositionalArgs = 2, numOfPositionalOnlyArgs = 3, parameterNames = {"value", "file", "version"}, keywordOnlyNames = "allow_code") @ArgumentClinic(name = "version", defaultValue = "CURRENT_VERSION", conversion = ClinicConversion.Int) + @ArgumentClinic(name = "allow_code", defaultValue = "true", conversion = ClinicConversion.Boolean) @GenerateNodeFactory - abstract static class DumpNode extends PythonTernaryClinicBuiltinNode { + abstract static class DumpNode extends PythonQuaternaryClinicBuiltinNode { @Override protected ArgumentClinicProvider getArgumentClinic() { return DumpNodeClinicProviderGen.INSTANCE; } @Specialization - static Object doit(VirtualFrame frame, Object value, Object file, int version, + static Object doit(VirtualFrame frame, Object value, Object file, int version, boolean allowCode, @Bind Node inliningTarget, @Bind PythonContext context, @Cached("createFor($node)") BoundaryCallData boundaryCallData, @@ -195,7 +195,7 @@ static Object doit(VirtualFrame frame, Object value, Object file, int version, Object savedState = BoundaryCallContext.enter(frame, threadState, boundaryCallData); byte[] data; try { - data = Marshal.dump(language, value, version); + data = Marshal.dump(language, value, version, allowCode); } catch (IOException e) { throw CompilerDirectives.shouldNotReachHere(e); } catch (Marshal.MarshalError me) { @@ -207,17 +207,18 @@ static Object doit(VirtualFrame frame, Object value, Object file, int version, } } - @Builtin(name = "dumps", minNumOfPositionalArgs = 1, parameterNames = {"value", "version"}) + @Builtin(name = "dumps", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 2, parameterNames = {"value", "version"}, keywordOnlyNames = "allow_code") @ArgumentClinic(name = "version", defaultValue = "CURRENT_VERSION", conversion = ClinicConversion.Int) + @ArgumentClinic(name = "allow_code", defaultValue = "true", conversion = ClinicConversion.Boolean) @GenerateNodeFactory - abstract static class DumpsNode extends PythonBinaryClinicBuiltinNode { + abstract static class DumpsNode extends PythonTernaryClinicBuiltinNode { @Override protected ArgumentClinicProvider getArgumentClinic() { return DumpsNodeClinicProviderGen.INSTANCE; } @Specialization - static Object doit(VirtualFrame frame, Object value, int version, + static Object doit(VirtualFrame frame, Object value, int version, boolean allowCode, @Bind Node inliningTarget, @Bind PythonContext context, @Cached("createFor($node)") BoundaryCallData boundaryCallData, @@ -226,7 +227,7 @@ static Object doit(VirtualFrame frame, Object value, int version, PythonContext.PythonThreadState threadState = context.getThreadState(language); Object savedState = BoundaryCallContext.enter(frame, threadState, boundaryCallData); try { - return PFactory.createBytes(language, Marshal.dump(language, value, version)); + return PFactory.createBytes(language, Marshal.dump(language, value, version, allowCode)); } catch (IOException e) { throw CompilerDirectives.shouldNotReachHere(e); } catch (Marshal.MarshalError me) { @@ -237,16 +238,22 @@ static Object doit(VirtualFrame frame, Object value, int version, } } - @Builtin(name = "load", minNumOfPositionalArgs = 1) + @Builtin(name = "load", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = "file", keywordOnlyNames = "allow_code") + @ArgumentClinic(name = "allow_code", defaultValue = "true", conversion = ClinicConversion.Boolean) @GenerateNodeFactory - abstract static class LoadNode extends PythonBuiltinNode { + abstract static class LoadNode extends PythonBinaryClinicBuiltinNode { + @Override + protected ArgumentClinicProvider getArgumentClinic() { + return MarshalModuleBuiltinsClinicProviders.LoadNodeClinicProviderGen.INSTANCE; + } + @NeverDefault protected static LookupAndCallBinaryNode createCallReadNode() { return LookupAndCallBinaryNode.create(T_READ); } @Specialization - static Object doit(VirtualFrame frame, Object file, + static Object doit(VirtualFrame frame, Object file, boolean allowCode, @Bind Node inliningTarget, @Bind PythonContext context, @Cached("createCallReadNode()") LookupAndCallBinaryNode callNode, @@ -258,7 +265,7 @@ static Object doit(VirtualFrame frame, Object file, throw raiseNode.raise(inliningTarget, PythonBuiltinClassType.TypeError, ErrorMessages.READ_RETURNED_NOT_BYTES, buffer); } try { - return Marshal.loadFile(language, file); + return Marshal.loadFile(language, file, allowCode); } catch (NumberFormatException e) { throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.BAD_MARSHAL_DATA_S, e.getMessage()); } catch (Marshal.MarshalError me) { @@ -267,13 +274,14 @@ static Object doit(VirtualFrame frame, Object file, } } - @Builtin(name = "loads", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"bytes"}) + @Builtin(name = "loads", minNumOfPositionalArgs = 1, numOfPositionalOnlyArgs = 1, parameterNames = {"bytes"}, keywordOnlyNames = "allow_code") @ArgumentClinic(name = "bytes", conversion = ClinicConversion.ReadableBuffer) + @ArgumentClinic(name = "allow_code", defaultValue = "true", conversion = ClinicConversion.Boolean) @GenerateNodeFactory - abstract static class LoadsNode extends PythonUnaryClinicBuiltinNode { + abstract static class LoadsNode extends PythonBinaryClinicBuiltinNode { @Specialization - static Object doit(VirtualFrame frame, Object buffer, + static Object doit(VirtualFrame frame, Object buffer, boolean allowCode, @Bind Node inliningTarget, @Bind PythonContext context, @Cached("createFor($node)") InteropCallData callData, @@ -287,7 +295,7 @@ static Object doit(VirtualFrame frame, Object buffer, if (!language.isSingleContext()) { cacheKey = language.cacheKeyForBytecode(bytes, length); } - return Marshal.load(language, bytes, length, cacheKey); + return Marshal.load(language, bytes, length, cacheKey, allowCode); } catch (NumberFormatException e) { throw raiseNode.raise(inliningTarget, ValueError, ErrorMessages.BAD_MARSHAL_DATA_S, e.getMessage()); } catch (Marshal.MarshalError me) { @@ -390,15 +398,20 @@ public final Throwable fillInStackTrace() { } @TruffleBoundary - static byte[] dump(PythonLanguage language, Object value, int version) throws IOException, MarshalError { - Marshal outMarshal = new Marshal(language, version); + static byte[] dump(PythonLanguage language, Object value, int version, boolean allowCode) throws IOException, MarshalError { + Marshal outMarshal = new Marshal(language, version, allowCode); outMarshal.writeObject(value); return outMarshal.outData.toByteArray(); } @TruffleBoundary static Object load(PythonLanguage language, byte[] ary, int length, long cacheKey) throws NumberFormatException, MarshalError { - Marshal inMarshal = new Marshal(language, ary, length, cacheKey); + return load(language, ary, length, cacheKey, true); + } + + @TruffleBoundary + static Object load(PythonLanguage language, byte[] ary, int length, long cacheKey, boolean allowCode) throws NumberFormatException, MarshalError { + Marshal inMarshal = new Marshal(language, ary, length, cacheKey, allowCode); Object result = inMarshal.readObject(); if (result == null) { throw new MarshalError(PythonBuiltinClassType.TypeError, ErrorMessages.BAD_MARSHAL_DATA_NULL); @@ -407,8 +420,8 @@ static Object load(PythonLanguage language, byte[] ary, int length, long cacheKe } @TruffleBoundary - static Object loadFile(PythonLanguage language, Object file) throws NumberFormatException, MarshalError { - Marshal inMarshal = new Marshal(language, file); + static Object loadFile(PythonLanguage language, Object file, boolean allowCode) throws NumberFormatException, MarshalError { + Marshal inMarshal = new Marshal(language, file, allowCode); Object result = inMarshal.readObject(); if (result == null) { throw new MarshalError(PythonBuiltinClassType.TypeError, ErrorMessages.BAD_MARSHAL_DATA_NULL); @@ -469,6 +482,7 @@ public int read(byte[] b, int off, int len) { final DataOutput out; final DataInput in; final int version; + final boolean allowCode; int depth = 0; long cacheKey; TruffleFile bytecodeFile; @@ -484,8 +498,13 @@ public int read(byte[] b, int off, int len) { Source source = null; Marshal(PythonLanguage language, int version) { + this(language, version, true); + } + + Marshal(PythonLanguage language, int version, boolean allowCode) { this.language = language; this.version = version; + this.allowCode = allowCode; this.outData = new ByteArrayOutputStream(); this.out = new DataOutputStream(outData); this.refMap = new HashMap<>(); @@ -496,6 +515,7 @@ public int read(byte[] b, int off, int len) { Marshal(PythonLanguage language, int version, DataOutput out) { this.language = language; this.version = version; + this.allowCode = true; this.outData = null; this.out = out; this.refMap = new HashMap<>(); @@ -504,24 +524,37 @@ public int read(byte[] b, int off, int len) { } Marshal(PythonLanguage language, byte[] in, int length, long cacheKey) { - this(language, SerializationUtils.createByteBufferDataInput(ByteBuffer.wrap(in, 0, length)), null, 0); + this(language, in, length, cacheKey, true); + } + + Marshal(PythonLanguage language, byte[] in, int length, long cacheKey, boolean allowCode) { + this(language, SerializationUtils.createByteBufferDataInput(ByteBuffer.wrap(in, 0, length)), null, 0, allowCode); this.cacheKey = cacheKey; } Marshal(PythonLanguage language, byte[] in, int length, long cacheKey, TruffleFile bytecodeFile, int baseOffset) { - this(language, SerializationUtils.createByteBufferDataInput(ByteBuffer.wrap(in, 0, length)), bytecodeFile, baseOffset); + this(language, SerializationUtils.createByteBufferDataInput(ByteBuffer.wrap(in, 0, length)), bytecodeFile, baseOffset, true); this.cacheKey = cacheKey; } Marshal(PythonLanguage language, Object in) { - this(language, new DataInputStream(new FileLikeInputStream(in)), null, 0); + this(language, in, true); + } + + Marshal(PythonLanguage language, Object in, boolean allowCode) { + this(language, new DataInputStream(new FileLikeInputStream(in)), null, 0, allowCode); } Marshal(PythonLanguage language, DataInput in, TruffleFile bytecodeFile, int baseOffset) { + this(language, in, bytecodeFile, baseOffset, true); + } + + Marshal(PythonLanguage language, DataInput in, TruffleFile bytecodeFile, int baseOffset, boolean allowCode) { this.language = language; this.in = in; this.refList = new ArrayList<>(); this.version = -1; + this.allowCode = allowCode; this.outData = null; this.out = null; this.refMap = null; @@ -923,6 +956,9 @@ private void writeComplexObject(Object v, int flag) { writeByte(ARRAY_TYPE_OBJECT); writeObjectArray((Object[]) v); } else if (v instanceof PCode c) { + if (!allowCode) { + throw new MarshalError(ValueError, ErrorMessages.MARSHALLING_CODE_OBJECTS_DISALLOWED); + } // we always store code objects in our format, CPython will not read our // marshalled data when that contains code objects writeByte(TYPE_GRAALPYTHON_CODE | flag); @@ -1151,10 +1187,19 @@ private Object readObject(int type, AddRefAndReturn addRef) throws NumberFormatE set.setDictStorage(setStore); return set; case TYPE_GRAALPYTHON_CODE: + if (!allowCode) { + throw new MarshalError(ValueError, ErrorMessages.UNMARSHALLING_CODE_OBJECTS_DISALLOWED); + } return addRef.run(readCode()); case TYPE_GRAALPYTHON_CODE_UNIT: + if (!allowCode) { + throw new MarshalError(ValueError, ErrorMessages.UNMARSHALLING_CODE_OBJECTS_DISALLOWED); + } return addRef.run(readRemovedCodeUnitPayload()); case TYPE_GRAALPYTHON_DSL_CODE_UNIT: + if (!allowCode) { + throw new MarshalError(ValueError, ErrorMessages.UNMARSHALLING_CODE_OBJECTS_DISALLOWED); + } return addRef.run(readBytecodeDSLCodeUnit()); case TYPE_DSL_SOURCE: return addRef.run(readSource()); diff --git a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java index 1b32d143f6..fdcd80ad66 100644 --- a/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java +++ b/graalpython/com.oracle.graal.python/src/com/oracle/graal/python/nodes/ErrorMessages.java @@ -140,6 +140,8 @@ public abstract class ErrorMessages { public static final TruffleString BAD_MARSHAL_DATA_S = tsLiteral("bad marshal data (%s)"); public static final TruffleString BAD_MARSHAL_DATA_EOF = tsLiteral("marshal data too short"); public static final TruffleString BAD_MARSHAL_DATA_NULL = tsLiteral("bad NULL object in marshal data"); + public static final TruffleString MARSHALLING_CODE_OBJECTS_DISALLOWED = tsLiteral("marshalling code objects is disallowed"); + public static final TruffleString UNMARSHALLING_CODE_OBJECTS_DISALLOWED = tsLiteral("unmarshalling code objects is disallowed"); public static final TruffleString BAD_MEMBER_DESCR_TYPE_FOR_P = tsLiteral("bad memberdescr type for %p"); public static final TruffleString BAD_OPERAND_FOR = tsLiteral("bad operand type for %s%s: '%p'"); public static final TruffleString BAD_VALUES_IN_FDS_TO_KEEP = tsLiteral("bad value(s) in fds_to_keep"); From 70c9ff99a1cac2c2e097d2587ed5384042426f62 Mon Sep 17 00:00:00 2001 From: Michael Simacek Date: Mon, 10 Aug 2026 11:02:26 +0200 Subject: [PATCH 8/8] Fix get_pypi_source.py shebang --- scripts/get_pypi_source.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/get_pypi_source.py b/scripts/get_pypi_source.py index 06a9d07d73..50f50ff306 100755 --- a/scripts/get_pypi_source.py +++ b/scripts/get_pypi_source.py @@ -1,3 +1,4 @@ +#!/usr/bin/python3 # Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # @@ -37,8 +38,6 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. -#!/usr/bin/python - import argparse import builtins import json