# Description Nullable reference types are now enabled once, in `common.props`, and every project in the repository builds warning-clean with them. **Configuration** - `<Nullable>enable</Nullable>` lives in `common.props`. All per-project `<Nullable>` settings are removed: 178 project files (enable, annotations, and the `disable` in the three Aot test projects). - 419 per-file `#nullable enable`/`restore` directives and the 4 `#nullable disable` directives are removed. - Kept on purpose: - The 5 `<auto-generated>` schema files (`UANodeSet.cs`, `UA Type Dictionary.cs`, `OPCBinarySchema.cs`, `SecuredApplication.cs`, `PInvokeHelper.cs`) keep `#nullable enable`. The compiler ignores the project setting in generated code, so the directive is required there. - `tests/Opc.Ua.Interop.LegacyPeer` keeps `disable`. It builds against the 1.5.378 packages and does not import the repository props. - `docs/DeveloperGuide.md` and `.github/copilot-instructions.md` now describe the central setting. **Migration** (the 43 unset and 13 annotations-only projects, roughly 10k diagnostics) - **Production code: annotated at the declarations.** Covers `tools/Opc.Ua.SourceGeneration.Core`, `src/Opc.Ua.Lds.Server` and the LDS sample. - Members, parameters and returns are nullable where null actually flows. - Null-check helpers carry `[NotNullWhen]` (`IsNull`, `IsValidXmlName`, `IsMethodTypeDesign`). - `CoreUtils.Format`, `Utils.Format`, `ITemplateWriter.WriteLine` take `params object?[]`. `Template.AddReplacement` takes `string?` (rendering already skips null replacements). - `IRegisteredServerStore.RegisterAsync` returns `Task<RegistrationEntry?>`: it returns null when a registration is removed. - Dereferences backed by an invariant (for example, the validated model) use `!`, so runtime behaviour is unchanged. - **Behavioural changes in production code** (small, deliberate): - `NodeSetToModelDesign` throws `InvalidOperationException` when the file is not a NodeSet. Before, it threw a NullReferenceException on the next line. - `ModelDesignValidator` starts its lookup tables empty instead of null. A redundant `if (m_nodes == null) return;` guard is gone. - **Test code: minimal annotations.** - `?` on values that can be null, `!` where the test asserts non-null. - `= null!` for fixture members assigned in SetUp. - `TryGetStructure<T>(out T? x)` where the inferred `T?` broke the `IEncodeable` constraint. - A few redundant `== null` checks on structs (`NodeId`, `ArrayOf<T>`) folded into the adjacent `IsNull`/`Count` checks. **Notes for reviewers** - The diff is large (~1,500 files) but almost entirely mechanical: `?`, `!` and directive removal. The non-annotation changes are listed above. - Known follow-up, not done here: `IModelDesign.Namespaces/TargetNamespace/NamespaceUris` stay non-nullable even though the validator returns null before it loads a dictionary. Making them nullable would ripple through every generator call site. ## Related Issues - Fixes #4439 ## Checklist - [ ] I have signed the [CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf) and read the [CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md) doc. - [ ] I have added tests that prove my fix is effective or that my feature works and increased code coverage. - [x] I have added all necessary documentation. - [x] I have verified that my changes do not introduce (new) build or analyzer warnings. - [ ] I ran **all** tests locally using the **UA.slnx** solution against at least .net **framework** and .net **10**, and all passed. - [ ] I fixed **all** failing and flaky tests in the CI pipelines and **all** CodeQL warnings. - [ ] I have addressed **all** PR feedback received. The "no new warnings" box covers net10.0 and net48: full non-incremental builds of `UA.slnx`, `tools/SourceGeneration.slnx`, `tools/MigrationAnalyzer.slnx` and the Aot test projects finished with 0 warnings and 0 errors under the repository's warnings-as-errors settings. net9.0 and net8.0 also had no nullable diagnostics, but they were not rebuilt after the final fixes. Tests have not been run locally yet. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
OneFuzz validator contract tests
These NUnit tests exercise the console CLI, not the validator's internal classes. They never load callback assemblies into the test host, submit jobs, upload drops, instrument assemblies, or use the shared fuzz harness.
Run with isolated SDK outputs
From the repository root, with the .NET 10 SDK and runtime installed:
$artifacts = Join-Path ([IO.Path]::GetTempPath()) ("onefuzz-contract-sdk-" + [Guid]::NewGuid().ToString("N"))
dotnet test fuzzing\OneFuzz\Opc.Ua.OneFuzz.Validator.Tests\Opc.Ua.OneFuzz.Validator.Tests.csproj -c Release -p:CustomTestTarget=net10.0 --artifacts-path $artifacts
Always supply a unique absolute --artifacts-path, including for a build or
restore of this project. It propagates to both project references. Do not use a
single shared BaseIntermediateOutputPath.
The one-time fixture also passes a separate absolute --artifacts-path to every
dotnet publish invocation. It publishes:
- The BCL-only
Opc.Ua.OneFuzz.TestTargetproject withContractDependencyOnly=true, producing a real companion library. - The same project normally, with
ContractDependencyPathpointing at that companion DLL. This produces a managed net10 callback library, portable PDBs,.deps.json,.runtimeconfig.json, and the actual companion dependency. - The validator console application.
The original dependency publish directory and SDK build outputs are then removed.
Every test copies the published callback closure into a new relocated drop with
spaces in its name. Neither a repository working directory nor the original
reference's HintPath can supply missing dependencies. Temporary drops, receipts,
and published fixture files are deleted at fixture teardown.
The fixture project's ordinary solution-build mode compiles its dependency helper directly. It requires no pre-generated binaries. The companion-library publish is a mode of the same project, not a third project.
Contracts
| Test file | Contracts |
|---|---|
ValidatorContractTests.cs |
Exact discovery, DLL-name normalization, published closure hashes, optional configuration, exact per-target paths/hashes/counts, zero skips, callback-produced markers and unchanged drop |
ValidatorContractTests.Manifest.cs |
Missing/extra callbacks, wrong class, malformed/duplicate targets and JSON, area selection, explicit manifest/config paths, new results directory and watchdog argument guards |
ValidatorContractTests.Paths.cs |
Every required corpus bucket, missing/empty corpus, dictionaries, casing, traversal/globs, overlapping paths, equal bytes at distinct paths and direct/nested links |
ValidatorContractTests.Closure.cs |
Missing DLL/dependency/deps/runtimeconfig/PDB, incorrect or corrupt portable PDBs, net10 relocatable runtime settings and forbidden SharpFuzz metadata |
ValidatorContractTests.Configuration.cs |
ConfigVersion3, exactly four libfuzzerDotNet fields, exact callback mapping, Skip/baseline policy, ownership fields, one job, container boundaries, explicit complete dependencies and dictionary-token union |
ValidatorContractTests.Profile.cs |
Explicit ownership-profile generation, single/merged dictionaries, exact target coverage, unresolved ownership failures, no overwrites, transactional dictionary rollback, and replay through generated configurations |
ValidatorContractTests.Replay.cs |
Callback exception, zero exit without a receipt, incorrect receipt identity/count/path/hash, and hard watchdog termination of a noncooperative synchronous callback |
All ownership metadata is synthetic (contract-tests.invalid, synthetic project
names, fixture-only numeric IDs). It must not be copied into a service config.
Negative controls are NOT service targets
Opc.Ua.OneFuzz.TestTarget.ContractCallbacks.NegativeControl is solely a test
fixture. Reserved input bytes deliberately throw, call Environment.Exit(0),
write incorrect completion receipts, or execute an endless synchronous CPU loop.
Never add this fixture assembly to a service manifest or upload it to OneFuzz.
The NUnit NegativeControl category is descriptive; these tests are not ignored
or [Explicit] and run with the rest of the suite.
Every child wait is asynchronous. An outer safety timeout kills only the exact process object (and its descendants). Watchdog tests distinguish this safety timeout from the validator's own watchdog, check that the callback started in a different process, and verify that the recorded PID/start-time identity is gone. A surviving negative-control process is killed by its exact handle and fails the test. No thread abort, sleeps, synchronous task waits, network calls, or ports are used.
On Windows the nonparallel fixture temporarily disables Windows Error Reporting for intentional child failures, restoring the test host's original error mode afterwards. Directory-link tests use privilege-free NTFS junctions; other platforms use directory symlinks.
Solution registration
The owner of the parent solution should register:
fuzzing/OneFuzz/Opc.Ua.OneFuzz.Validator.Tests/Opc.Ua.OneFuzz.Validator.Tests.csprojfuzzing/OneFuzz/Opc.Ua.OneFuzz.TestTarget/Opc.Ua.OneFuzz.TestTarget.csproj
This change intentionally does not edit the root solution, parent manifest, CI, shared properties, validator source, or validator project.