Skip to content

fix(acl): make ResolvedCommand referenced_by unconditional (fix #15406) - #15603

Closed
tenderdeve wants to merge 2 commits into
tauri-apps:devfrom
tenderdeve:fix/15406-unconditional-referenced-by
Closed

fix(acl): make ResolvedCommand referenced_by unconditional (fix #15406)#15603
tenderdeve wants to merge 2 commits into
tauri-apps:devfrom
tenderdeve:fix/15406-unconditional-referenced-by

Conversation

@tenderdeve

@tenderdeve tenderdeve commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Closes #15406.
Closes #13865

ResolvedCommand::referenced_by and ResolvedCommandReference were gated behind #[cfg(debug_assertions)]. Because a proc-macro's cfg(debug_assertions) reflects the macro's own host build (not the consumer's target), generate_context!'s ToTokens could emit a struct literal whose shape disagreed with the consumer's view of ResolvedCommand.

Under mismatched profiles — e.g.:

[profile.release.build-override]
debug-assertions = true
[profile.release.package."*"]
debug-assertions = false

cargo build --release fails with error[E0560]: struct ... has no field named referenced_by.

Fix: drop the cfg(debug_assertions) gating on the field, on ResolvedCommandReference, on the referenced_by_permission_identifier plumbing, and on the ToTokens emission, so the resolved struct shape is identical across build profiles. Cost is two Strings per command, diagnostic-only.

Tested: cargo test -p tauri-utils --features build (41 passing), plus cargo check -p tauri in both debug and release.

…-apps#15406)

The referenced_by field and ResolvedCommandReference were gated behind
cfg(debug_assertions), so generate_context!'s ToTokens output depended on
the proc-macro host's debug-assertions rather than the consumer's target.
Under mismatched profiles (e.g. profile.release.build-override with
debug-assertions = true) the emitted struct literal referenced a field
the consumer could not see, failing with E0560. Make the field and its
token emission unconditional so the resolved struct shape no longer
varies by build profile.
@tenderdeve
tenderdeve requested a review from a team as a code owner June 29, 2026 09:54
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Package Changes Through 95c5dff

There are 14 changes which include tauri with minor, tauri-cli with minor, @tauri-apps/cli with minor, tauri-bundler with minor, tauri-utils with minor, tauri-build with minor, tauri-macos-sign with minor, tauri-runtime-wry with minor, tauri-runtime with minor, tauri-codegen with minor, tauri-macros with minor, tauri-plugin with minor, tauri-driver with minor, @tauri-apps/api with minor

Planned Package Versions

The following package releases are the planned based on the context of changes in this pull request.

package current next
@tauri-apps/api 2.11.1 2.12.0
tauri-utils 2.9.3 2.10.0
tauri-macos-sign 2.3.4 2.4.0
tauri-bundler 2.9.4 2.10.0
tauri-runtime 2.11.3 2.12.0
tauri-runtime-wry 2.11.4 2.12.0
tauri-codegen 2.6.3 2.7.0
tauri-macros 2.6.3 2.7.0
tauri-plugin 2.6.3 2.7.0
tauri-build 2.6.3 2.7.0
tauri 2.11.5 2.12.0
@tauri-apps/cli 2.11.4 2.12.0
tauri-cli 2.11.4 2.12.0
tauri-driver 2.0.6 2.1.0

Add another change file through the GitHub UI by following this link.


Read about change files or the docs at github.com/jbolda/covector

@Legend-Master Legend-Master added this to the 2.12 milestone Jul 2, 2026

@Legend-Master Legend-Master left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tenderdeve

Copy link
Copy Markdown
Contributor Author

@Legend-Master right that the cfg lives entirely in tauri-utils — the catch is tauri-utils gets compiled twice with two different debug_assertions values, and the cfg is evaluated independently in each:

  • Host copygenerate_context! (tauri-macros → tauri-codegen → tauri_utils::acl::resolved::build::ToTokens) runs in the proc-macro, so it's compiled under the build-override profile. Its debug_assertions decides whether the emitted struct literal includes referenced_by.
  • Target copy — the ResolvedCommand struct the emitted tokens must match is compiled under the app's normal profile. Its debug_assertions decides whether the field exists.

In a plain cargo build / cargo build --release both copies share the same debug_assertions, so they agree and nothing breaks — that's why it doesn't reproduce normally. It only breaks when the two profiles disagree, which build-override lets you do.

Minimal repro (any tauri v2 app), add to src-tauri/Cargo.toml:

[profile.release.build-override]
debug-assertions = true

then cargo tauri build (or cargo build --release in src-tauri). Host proc-macro now has debug_assertions = true → emits referenced_by; runtime tauri-utils is release (debug_assertions = false) → struct has no such field:

error[E0560]: struct `tauri::utils::acl::resolved::ResolvedCommand` has no field named `referenced_by`

Flip it (profile.dev.build-override.debug-assertions = false + cargo build) and you get the mirror error where the token stream omits a field the struct requires. Making the field unconditional removes the profile dependence entirely; cost is one extra ResolvedCommandReference (two Strings) per resolved command in release. Happy to instead gate the emission on the target profile if you'd rather keep it debug-only, but there's no clean way for a proc-macro to read the consumer's debug_assertions, so unconditional looked like the robust fix.

@Legend-Master

Copy link
Copy Markdown
Contributor

That sounds right in theory, but I can't seem to reproduce it...

@tenderdeve

Copy link
Copy Markdown
Contributor Author

@Legend-Master got a self-contained repro. Two gotchas that make it not reproduce:

  1. [profile.*.build-override] only takes effect from the workspace root (or a standalone src-tauri). If src-tauri is a workspace member, Cargo silently ignores the profile override — no warning in some setups. That's the most likely reason it didn't repro for you.
  2. Needs a clean rebuild so generate_context! actually re-expands under the new host profile (cargo clean or touch the macro input).

You don't even need tauri to see the class of bug — here's a 3-crate workspace that reproduces the exact E0560. The shared crate mirrors ResolvedCommand (cfg-gated field), pmac mirrors the ToTokens codegen:

Cargo.toml (workspace root):

[workspace]
members = ["shared", "pmac", "app"]
resolver = "2"
[profile.release.build-override]
debug-assertions = true

shared/src/lib.rs:

#[derive(Default)]
pub struct Cmd {
    pub context: u32,
    #[cfg(debug_assertions)]
    pub referenced_by: String,
}

pmac/src/lib.rs (proc-macro, deps: shared):

use proc_macro::TokenStream;
#[proc_macro]
pub fn make_cmd(_: TokenStream) -> TokenStream {
    let mut fields = String::from("context: 1u32,");
    if cfg!(debug_assertions) {              // HOST copy of `shared`, under build-override
        fields.push_str("referenced_by: String::new(),");
    }
    format!("shared::Cmd {{ {fields} }}").parse().unwrap()
}

app/src/main.rs (deps: shared, pmac):

fn main() { let _c: shared::Cmd = pmac::make_cmd!(); }

cargo build --release:

error[E0560]: struct `Cmd` has no field named `referenced_by`
 --> app/src/main.rs:2:27

Host proc-macro copy of shared compiles with debug_assertions = true (build-override) → emits referenced_by; target copy is release = false → struct lacks the field. That's precisely what generate_context!tauri-codegentauri_utils::acl::resolved::build::ToTokens does vs the ResolvedCommand linked into the app. Making the field unconditional removes the profile dependence for one ResolvedCommandReference (two Strings) per resolved command in release — happy to gate on the target profile instead if you'd prefer debug-only, but a proc-macro can't read the consumer's debug_assertions, so unconditional was the robust option.

@Legend-Master

Copy link
Copy Markdown
Contributor

Could you record a short video on the repro steps? Like from create-tauri-app to adding what to the Cargo.toml and using what to build and the final error.

@tenderdeve

Copy link
Copy Markdown
Contributor Author

@Legend-Master can't easily attach a video, but here's the full copy-pasteable transcript from create-tauri-app to the error — real tauri, no synthetic crates. macOS, cargo 1.96.0, create-tauri-app@4.6.2, tauri-utils 2.9.3.

$ npm create tauri-app@latest repro -- --template vanilla --manager pnpm --yes
Template created!

$ cd repro/src-tauri

# append the build-override that lets host & target profiles disagree
$ printf '\n[profile.release.build-override]\ndebug-assertions = true\n' >> Cargo.toml

$ cargo build --release
   Compiling repro v0.1.0 (.../repro/src-tauri)
error[E0422]: cannot find struct, variant or union type `ResolvedCommandReference` in module `::tauri::utils::acl::resolved`
  --> src/lib.rs:12:14
   |
12 |         .run(tauri::generate_context!())
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^ not found in `::tauri::utils::acl::resolved`
   |
note: found an item that was configured out
  --> .../tauri-utils-2.9.3/src/acl/resolved.rs:25:12
   |
23 | #[cfg(debug_assertions)]
   |       ---------------- the item is gated here
25 | pub struct ResolvedCommandReference {
   |            ^^^^^^^^^^^^^^^^^^^^^^^^

error[E0560]: struct `ResolvedCommand` has no field named `referenced_by`
  --> src/lib.rs:12:14
   |
12 |         .run(tauri::generate_context!())
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^^ `ResolvedCommand` does not have this field
   |
   = note: this error originates in the macro `tauri::generate_context`

error: could not compile `repro` (lib) due to 2 previous errors

Three things that make it not reproduce (likely why it didn't for you):

  1. src-tauri must be the manifest root where the [profile.*.build-override] lives (default create-tauri-app layout — good). If src-tauri is a workspace member, Cargo ignores profile overrides in the member manifest, so the host/target profiles never diverge and it builds fine.
  2. --release — the override flips the host build-script/proc-macro profile to debug_assertions = true while the target crate stays release (= false). Both halves of tauri-utils must disagree; a plain cargo build keeps them equal.
  3. Clean expansion — first --release build already re-expands generate_context! under the new host profile, so no cargo clean needed here, but if you'd already built release before adding the override, touch src/lib.rs or cargo clean.

The mirror case (profile.dev.build-override.debug-assertions = false + cargo build) gives the inverse error where the token stream omits a field the struct requires. Making the field unconditional removes the profile dependence entirely.

@Legend-Master

Copy link
Copy Markdown
Contributor

I see why 🤦‍♂️, the project I used to test things didn't have any allowed permissions, thanks for the repro

@tenderdeve

tenderdeve commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@Legend-Master thanks for confirming. green now except the two android jobs, and those are a cross pre-compile infra thing, nothing to do with this.

review's still on changes-requested from before the repro - could you take another look when you have a sec?

and if you'd rather keep referenced_by debug-only i can gate it on the target profile instead, just went unconditional since the proc-macro can't read the consumer's debug_assertions. either way works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants