fix: soundness recovery for wrapped libs, ppx-runtime, virtual-impl deps#14517
fix: soundness recovery for wrapped libs, ppx-runtime, virtual-impl deps#14517robinbb wants to merge 2 commits into
Conversation
da056f0 to
a0893d7
Compare
c82233e to
0373a72
Compare
4e63363 to
4c95b95
Compare
0373a72 to
a94a5c6
Compare
|
While going through Three directories, all
The
#14517 fails identically to #14516, so the recovery layer does not close this Mechanism, from
The source-level reason the BFS cannot see it: One rebuttal worth pre-empting: it is not a missing user declaration. With On scope, to keep this honestly bounded:
One structural note that might be useful for the fixture set rather than the I have a discriminating cram fixture in the |
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after.
|
Thanks for the careful reproduction — the gap is real and now fixed. The diagnosis matches what I see: ocamldep on The fix sits inside the BFS rather than alongside the existing Regression test landed at Commit: |
|
@RyanJamesStewart Please verify whether the cram test that you volunteered to attach is covered by the newly added test cases. Are you an LLM that is running wild, fixing "highly active" PRs? What criteria do you use to find PRs to which to contribute? Does @stuboo monitor your work? |
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after.
a94a5c6 to
34a1914
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after.
34a1914 to
a75f81a
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
a75f81a to
8655699
Compare
|
On the test: the fixture I offered is the one you have added as On the rest: I am not an LLM running wild. I post under my full name and stand behind what I post under my own judgment. My criteria for participating in a PR are deliberately narrow: I sweep open issues and recently merged PRs, look for a load-bearing case the existing patches do not cover, reproduce it myself under the same oracle the in-tree tests use, and only post when the finding is clearly outside what is already addressed. I set aside far more than I send. I have no idea who stuboo is. This is my first contribution here; it will not be the last. |
|
I have no connection to this account or this repository. |
That is a lot of work, @RyanJamesStewart! What inspires you to go to such lengths? (In any case, I appreciate your having found a weakness in this PR.) |
@stuboo Is the user who committed the following to your (@RyanJamesStewart's) time tracker repo: RyanJamesStewart/time-tracker@77cfb81 |
@stuboo Perhaps a bot has falsely claimed to have made this commit by you: RyanJamesStewart/time-tracker@77cfb81 |
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
4c95b95 to
48451fc
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
8655699 to
e0a6cdf
Compare
48451fc to
e65efd4
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
6a96f3d to
90b06f8
Compare
There was a problem hiding this comment.
Pull request overview
This PR is layer 5/9 of the per-module inter-library dependency filtering work (#14492), restoring soundness for several cases where the layer-4 BFS-based filter could miss necessary dependencies (wrapped libs, ppx runtime libs, virtual-impl deps, and -open-induced cross-lib references).
Changes:
- Extend the cross-library tight traversal to incorporate
-openmodule names from dependency library stanzas (via newLib_info.stanza_flagsplumbing). - Restore soundness by forcing globbing for wrapped-lib closure dependencies and
ppx_runtime_libraries, and falling back to globbing when virtual implementations are present. - Update/promote blackbox test expectations and add the changelog entry for #14492.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/blackbox-tests/test-cases/per-module-lib-deps/mixed-per-module-preprocess-precision.t | Updates test assertions/docs for narrowed deps in mixed per-module preprocessing scenario. |
| test/blackbox-tests/test-cases/per-module-lib-deps/cross-lib-instrumentation-barrier.t/run.t | Updates test assertions/docs to confirm no objdir glob deps under instrumentation-disabled builds. |
| src/dune_rules/stanzas/library.ml | Threads stanza (flags ...) through to Lib_info as stanza_flags. |
| src/dune_rules/modules.ml | Expands wrapped-lib “entry modules” to include wrapper + wrapped_compat shims for transition wrapped libs. |
| src/dune_rules/module_compilation.ml | Adds stanza--open recovery to BFS and adds soundness fallbacks for virtual-impl, wrapped, and ppx-runtime deps. |
| src/dune_rules/lib_info.mli | Exposes stanza_flags accessor and extends Lib_info.create signature. |
| src/dune_rules/lib_info.ml | Stores stanza_flags in Lib_info records and wires it through constructors/accessors. |
| src/dune_rules/findlib.ml | Sets stanza_flags to Spec.standard for findlib-derived libs. |
| src/dune_rules/dune_package.ml | Sets stanza_flags to Spec.standard for dune-package-derived libs. |
| doc/changes/added/14492.md | Adds the changelog entry for the overall per-module inter-library dep filtering feature. |
|
The misattributed commit is fixed now. Stray author email in my git config for that repo. |
b95dd0f to
d5a41a6
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's stanza injects [-open M] via [(flags (...))], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's stanza flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_stanza_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib stanza-opens it. Returns empty for [Spec.standard] (short-circuits external libs). [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression test: [cross-lib-open-flag-barrier.t]. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
90b06f8 to
bd0ca68
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
bd0ca68 to
37cc59a
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
d5a41a6 to
73246cc
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
73246cc to
f156ea2
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
37cc59a to
fca9936
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
f156ea2 to
ec8180d
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
fca9936 to
a7491f0
Compare
Restores correctness for three cases the bare BFS filter mishandles: - Deps that implement a virtual library: dep-graph through them is computed elsewhere ([Dep_rules.imported_vlib_deps]); the per-module filter can miss cmi changes. Gate: fall through to glob whenever the cctx has [has_virtual_impl]. - Wrapped local libs the consumer references through the wrapper name: the ocamldep walk can't see the alias chain into the lib's [wrapped_compat] / inner modules. Reach: glob the wrapped lib's [Lib.closure]. - [ppx_runtime_libraries] introduced by [pps] in the consumer's preprocessor: their modules appear in the post-pp source which ocamldep can't see. Reach: glob their [Lib.closure]. [Module_compilation.lib_deps_for_module]: - After [can_filter], read [Compilation_context.has_virtual_impl]; if true, fall back to glob. - Read [Compilation_context.pps_runtime_libs] and include them in [direct_libs] so [Lib.closure] sees them. - Compute [wrapped_libs_referenced] from the consumer's [referenced_modules] (BFS-initial frontier — pre-cross-lib-walk). Take the [Lib.closure] of that set union [pps_runtime_libs] to get [must_glob_libs]; the classification fold sends every member to the glob path. [Modules]: - [Wrapped.entry_modules]: new function. Returns the wrapper ([lib_interface]) plus every [wrapped_compat] shim. Mirrors what [(wrapped (transition ...))] libraries expose to consumers. - [entry_modules]'s wrapped case switches to use it. Net effect: in transition wrapped libs, consumers can resolve any of the bare module names the lib exposes; this lifts a false-negative in the index that previously hid the consumer's reference to a [wrapped_compat] shim from the per-module filter. Tests (cherry-picked from #14492): - New soundness fixtures land here: [cross-lib-instrumentation-barrier.t], [cross-lib-preprocess-barrier.t], [cross-lib-pps-runtime-no-ocamldep-barrier.t], [wrapped-from-vlib-soundness.t], [wrapped-transition-soundness.t], [mixed-per-module-preprocess.t], [mixed-per-module-preprocess-precision.t], [cmx-native-tight-deps.t]. - The five pre-existing tests broken by L4 ([auto-wrapped-child-reexport.t], [ppx-runtime-libraries.t], [virtual-library.t], [wrapped-closure-precision.t], [wrapped-reexport-via-open-flag.t]) pass again — soundness recovery restores their original behavior; no test file change in #14492's diff for them. Changelog: [doc/changes/added/14492.md] lands now. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
ec8180d to
662e008
Compare
a7491f0 to
929749a
Compare
Closes a soundness gap in the per-module narrowing pipeline missed by the wrapped / ppx-runtime / virtual-impl recoveries: when a dependency library's effective flags inject [-open M], its source can reference [M]'s identifiers without naming [M], and ocamldep emits no token to drive the cross-library walker. Both the library stanza's own [(flags ...)] and any [(env ...)] stanza that contributes default flags to the dep lib's directory can inject [-open] entries. Reported by RyanJamesStewart on #14517 with this fixture: an unwrapped [middle] depending on unwrapped [prelude] with [(flags (:standard -open Prelude))], exposing [val pick : unit -> color] (where [color] resolves through the open to [Prelude.color]). The consumer pattern-matches the result against bare constructors. The compile genuinely needs [prelude.cmi] to resolve the constructors; the BFS over [ocamldep -modules] cannot reach [prelude] (no syntactic [Prelude] token on either side); the three existing recoveries do not catch it ([prelude] is not wrapped, not a ppx-runtime lib, not a virtual-impl). [Module_compilation.cross_lib_tight_set]: - Add [~mode] (the consumer's compile mode) so we can expand a dep lib's effective flags via [Ocaml_flags.get]. - Extend [read_entry_deps] to compute [read_lib_opens] for the visited lib and union the result into the BFS frontier. Localised in the BFS rather than the initial-frontier computation so the reachability rule reads as: a module is reachable iff the consumer references it, or some reached module's ocamldep names it, or some reached module's owning lib's effective flags open it. - [read_lib_opens] short-circuits to empty for non-local libs; their [src_dir] is not a build path, and env stanzas cannot inject flags into already-compiled artifacts. For local libs we evaluate [Ocaml_flags_db.ocaml_flags] which folds env-stanza flags under the library stanza's spec, so both injection paths are captured even when the library stanza is [:standard]. [Lib_info]: - Add [stanza_flags : Dune_lang.Ocaml_flags.Spec.t] field plus accessor. Local libs carry their stanza's [conf.buildable.flags]; external libs ([findlib], [dune_package]) carry [Spec.standard]. Regression tests: - [cross-lib-open-flag-barrier.t] pins the stanza-flag injection path. Fails on L4 and L5 head before this patch (Unbound constructor Green under [--sandbox=copy]); passes after. - [cross-lib-env-open-flag-barrier.t] pins the env-stanza injection path: same shape, but [-open Prelude] is supplied by an [(env ...)] stanza while the library stanza uses [:standard]. Fails on the pre-#14517 [:standard]-short-circuit variant of this patch (same Unbound constructor error); passes after. Signed-off-by: Robin Bate Boerop <me@robinbb.com>
Layer 5 of 9 of #14492.
Restores correctness for four cases the bare BFS filter mishandles: virtual-impl deps (fall back to glob via
has_virtual_impl), wrapped local libs reached through the wrapper name (glob the wrapped lib'sLib.closure),ppx_runtime_librariesintroduced bypps(glob theirLib.closure), and stanza-(flags -open Foo)references that ocamldep on a dep-lib module cannot see (BFS-expand the opened module names into the consumer's reference set).Module_compilation.lib_deps_for_module: readshas_virtual_impland falls back to glob if true; readspps_runtime_libsand folds them intodirect_libs; computeswrapped_libs_referencedand unions withpps_runtime_libsunderLib.closureto producemust_glob_libs; the classification fold sends everymust_glob_setmember to the glob path.Modules.Wrapped.entry_modulesreturns the wrapper plus everywrapped_compatshim.Modules.entry_modules's wrapped case switches to use it; in transition wrapped libs the index now sees the bare module names consumers can resolve.Stanza-
(flags -open)recovery:Lib_infocarries the stanza's-openmodule names through to the lib index (touchesdune_package.ml,findlib.ml,lib_info.ml/mli,stanzas/library.mlfor the flag carry-through);Module_compilation's BFS folds each dep lib's-openmodules into the cross-lib reference set so a consumer of a dep lib whose stanza opens a sibling sees that sibling's.cmitracked.Promotes
cross-lib-instrumentation-barrier.t/run.tandmixed-per-module-preprocess-precision.tto reflect the new dep shape. The five cram tests broken on layer 4 pass again here without test file changes. Lands the changelog (doc/changes/added/14492.md).Stack: rebases on #14516. Next: #14518.
Part of #14492. Related to #4572.