Debug actions: Skip methods Log all patch cannot instrument - #981
Open
M-r-A wants to merge 1 commit into
Open
Conversation
Log all patch attaches a call logger to every method the assembly declares. Harmony patches by emitting a wrapper around the original's body, and an extern method has none, so the wrapper is malformed and the runtime rejects it with InvalidProgramException. The assembly declares two, both P/Invokes used to hide the headless arbiter's window. Reaching the first threw out of the loop, so every method not yet reached was left uninstrumented while the action still emitted a large, plausible-looking trace with no sign that coverage was partial. Which methods survived depended on the order DefinedTypes happened to return, so the boundary moved between builds. Exclude externs and methods still carrying open generic parameters, and guard the Patch call. The filter cannot anticipate every reason Harmony may refuse a method, and this action is wanted precisely when something is already wrong, so it should lose one entry rather than the whole trace. The count is reported once at the end, and the first refusal in full. The decision moves to Common so it can be covered by a test; the test project cannot reference the client, which needs the game's assemblies.
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.
Problem
Multiplayer -> Log all patch, and its main-menu variantLog all patch entry,attach a call logger to every method the multiplayer assembly declares. The
action throws on the first extern method it reaches, the loop aborts, and
every method not yet reached is left uninstrumented — while the action still
emits a large, plausible-looking trace with no sign that coverage is partial.
A reader reasonably concludes that any method absent from the trace was never
called, when it may simply never have been instrumented. Which methods survive
depends on the order
DefinedTypeshappens to return, so the coverage boundaryis arbitrary and can move between builds.
Measured on the same main menu, before and after:
InvalidProgramExceptionTypes that were previously never reached include
Desyncs.DeferredStackTracing,ModCompatibilityManager,Patches.MarkLongEventsandPatches.NewLongEvent—the desync and long-event machinery this action is most often used to trace.
Root cause
Harmony patches by emitting a wrapper around the original's body. An extern
method has no body, so the wrapper is malformed and the runtime rejects it.
The filter in
LogAllPatchexcludes generics, property getters, abstracts anddelegate members, but not externs, and the
Patchcall is not guarded:The assembly declares two extern methods —
ArbiterWindowFix'sDllImport("User32")declarations, used to hide the headless arbiter's window.This does not depend on the host operating system: the loop reflects over them
rather than calling them, and a P/Invoke declaration is unpatchable everywhere.
Fix
Exclude extern methods (
MethodAttributes.PinvokeImpl) and methods stillcarrying open generic parameters, alongside the exclusions already present.
Guard the
Patchcall as well. The filter cannot anticipate every reason Harmonymay refuse a method, and this action is wanted precisely when something is
already wrong, so it should lose one entry rather than the whole trace. The count
is reported once at the end, and the first refusal in full, so a systematic
problem stays visible without burying the trace under one line per method.
The decision moves to
Multiplayer.Common.InstrumentationTargetsso it can becovered by a test — the test project cannot reference the client, which needs the
game's assemblies.
Testing
InstrumentationTargetsTestuses a stand-in extern declaration that isnever called, so it needs no library and behaves the same on every platform.
Log all patch entrycompletes withno exception, and produces the coverage in the table above.
Notes
fails" PR by exactly one line — both add
using Multiplayer.Common;toDebugActions.cs, at the same position, so whichever merges first leaves theother clean.