[#85] Track dangling references in analyze#96
Merged
Conversation
Add a dangling_refs table and dangling_refs_view recording references whose target object was assigned an id but never written to the objects table because its serialized file was not part of the analyzed input (partial bundle sets, unity default resources, ContentDirectory builds without ContentLayout.json). Every object id now resolves to exactly one of objects or dangling_refs. Detection runs after all files are processed via a new ISQLiteFileParser.FinalizeDatabase() hook: the writer reads back the written object/file ids and records any assigned id with no objects row, adding a serialized_files row (archive NULL) for each un-analyzed target file so its name is available. Honors --skip-references. Schema user_version bumped 4 -> 5. Existing serialized_files count assertions now count analyzed files only (equal to the previous golden values, so no ExpectedData regeneration).
The writer resolved a component's m_GameObject PPtr to an object id even when it was null (m_FileID 0 and m_PathID 0), allocating a phantom (file, 0) object that no row is ever written for. With dangling_refs tracking this surfaced as confusing object_id 0 entries pointing at already-analyzed files. Only resolve m_GameObject when the PPtr is non-null; otherwise leave the game_object column empty, as the no-m_GameObject branch already does. A complete AssetBundle build now has zero dangling_refs, and every dangling target is reachable through refs, so dangling_refs_view covers them all. Tests assert dangling_refs has no object_id 0 rows.
Editor-only assets (ShaderSubGraph, .preset) can appear in an AssetBundle's m_Container with a null PPtr (m_FileID 0 and m_PathID 0) because they have no runtime object. The non-scene branch resolved that to a phantom (file, 0) object id and wrote an assetbundle_assets row pointing at it, which then surfaced as a false dangling reference with object_id 0 into an already-analyzed file. Skip container entries whose PPtr is null. assetbundle_asset_view already dropped these rows via its INNER JOIN, so this only removes rows that pointed at a non-existent object. Verified on a large Addressables build: the false object_id 0 dangling refs are gone, leaving only the expected unity default resources targets.
Contributor
There was a problem hiding this comment.
Pull request overview
Adds first-class tracking for unresolved (dangling) Unity object references during analyze by persisting them into the SQLite schema, so object-id gaps become explainable and reference metadata (target serialized file + LFID) is preserved.
Changes:
- Introduces
dangling_refstable +dangling_refs_view, and bumps analyzer DBuser_versionto 5. - Adds a post-parse
ISQLiteFileParser.FinalizeDatabase()hook and implements dangling-ref detection/writeback inSerializedFileSQLiteWriter. - Fixes two null-PPtr cases (null
m_GameObjectand nullAssetBundle.m_Containerentries) and adds targeted test coverage + docs updates.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| UnityDataTool.Tests/UnityDataToolPlayerDataTests.cs | Adjusts serialized_files counting to “analyzed only” and adds a player-build dangling-refs regression test. |
| UnityDataTool.Tests/UnityDataToolAssetBundleTests.cs | Updates serialized_files count assertion to exclude referenced-but-not-analyzed target files. |
| UnityDataTool.Tests/ExpectedDataGenerator.cs | Updates expected serialized_files count generation to match new semantics. |
| UnityDataTool.Tests/AnalyzeDanglingRefsTests.cs | New test fixture validating dangling refs for partial vs full AssetBundle analyzes and id-space invariants. |
| Documentation/analyzer.md | Documents dangling_refs / dangling_refs_view and clarifies prior “dangling id” behavior. |
| Analyzer/Util/IdProvider.cs | Exposes id-provider mappings to support finalization-time dangling ref enumeration. |
| Analyzer/SQLite/Writers/SerializedFileSQLiteWriter.cs | Implements null-PPtr m_GameObject fix and adds FinalizeDatabase() to write dangling refs + target serialized_files rows. |
| Analyzer/SQLite/Parsers/SerializedFileParser.cs | Wires parser-level FinalizeDatabase() into the serialized-file pipeline. |
| Analyzer/SQLite/Parsers/AddressablesBuildLayoutParser.cs | Implements no-op FinalizeDatabase() for build-layout parser. |
| Analyzer/SQLite/Handlers/ISQLiteHandler.cs | Extends ISQLiteFileParser with FinalizeDatabase() hook. |
| Analyzer/SQLite/Handlers/AssetBundleHandler.cs | Skips editor-only m_Container entries with null PPtr to avoid phantom ids. |
| Analyzer/SQLite/Commands/SerializedFile/AddDanglingRef.cs | Adds insert command for the new dangling_refs table. |
| Analyzer/Resources/Init.sql | Adds dangling_refs table, dangling_refs_view, and bumps PRAGMA user_version to 5. |
| Analyzer/AnalyzerTool.cs | Calls FinalizeDatabase() for all parsers before running DB finalize script. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Andrew Skowronski <86242170+SkowronskiAndrew@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #85.
When
analyzewalks a SerializedFile and finds a reference (PPtr) to an object whose serialized file was not part of the analyzed input, it still assigns that target an object id but never writes anobjectsrow for it. Until now the only trace of these targets was an unexplained gap in the otherwise-dense object id sequence, and the information the reference actually carried — the target's serialized file name and local file id (LFID) — was discarded.This adds a
dangling_refstable anddangling_refs_viewthat record those unresolved targets, so every object id now resolves to exactly one ofobjectsordangling_refs, and the referenced file/LFID are preserved. Common cases: analyzing a single AssetBundle or a partial bundle group (cross-bundle references dangle), a Player build referencingunity default resources(shipped without TypeTrees, never analyzed), and ContentDirectory builds analyzed withoutContentLayout.json.Two pre-existing bugs in how null PPtrs were handled surfaced once these gaps became visible, and are fixed here too (see Changes).
Changes
dangling_refs table + view
dangling_refs— one row per referenced object that got an id but noobjectsrow. Columns mirrorobjects(id,object_id= target LFID,serialized_file) so the two tables partition the id space.dangling_refs_view— resolves each dangling target back to the object(s) that reference it, one row per reference (source_id,source_serialized_file,source_object_id,property_path,property_type,target_id,target_serialized_file,target_object_id).serialized_files(witharchiveNULL and no objects of their own) so their name is available.ISQLiteFileParser.FinalizeDatabase()hook called fromAnalyzerToolbefore the database is finalized: the writer reads back the written object/file ids and records any assigned id that never became an object. It honors--skip-references(in that mode neitherrefsnordangling_refsare populated).user_versionbumped 4 → 5.Null-PPtr fixes (bugs the feature exposed)
m_GameObject: the writer resolved a component'sm_GameObjecteven when it was a null PPtr (m_FileID 0,m_PathID 0— expected for any non-component object), allocating a phantom(file, 0)id. Now only resolved when non-null; otherwise thegame_objectcolumn is left empty, as the no-m_GameObjectbranch already did.m_Containerentries: editor-only assets (ShaderSubGraph,.preset) can appear in an AssetBundle'sm_Containerwith a null PPtr because they have no runtime object.AssetBundleHandlerresolved these to a phantom id and wrote anassetbundle_assetsrow pointing at it. These entries are now skipped;assetbundle_asset_viewalready excluded them via its INNER JOIN, so only rows that pointed at a non-existent object are dropped.Docs
Documentation/analyzer.md— newdangling_refs/dangling_refs_viewsections and a cross-link from the existing "dangling" note underpreload_dependencies.Testing
dotnet test— full suite green (UnityFileSystem.Tests 425, Analyzer.Tests 62, UnityDataTool.Tests 241).AnalyzeDanglingRefsTests(partial vs. full LeadingEdge AssetBundle analyze) andUnityDataToolPlayerDataTests(Player build →unity default resources): assert the id-space accounting invariant (everyrefs.referenced_objectresolves toobjectsordangling_refs, and the two never overlap) and that no phantomobject_id = 0rows are recorded.serialized_filescount assertions now count analyzed files only (equal to the previous golden values, so noExpectedDataregeneration was needed).analyzeon a large Addressables customer build. Before the null-PPtr fixes it produced two falseobject_id = 0dangling refs into already-analyzed CABs; after,dangling_refscontains only the expectedunity default resourcestargets, with the accounting invariants intact.