pardeike/Harmony · error · ArgumentException

Instruction is not a load or store

Error message

Instruction is not a load or store

What it means

Harmony's LocalIndex() extension reads the local-variable slot targeted by an IL instruction, unwrapping a LocalBuilder operand or converting an integer operand. It throws ArgumentException when the instruction's opcode is neither a local load (ldloc/ldloca variants) nor a local store (stloc variants), because only those opcodes carry a local index operand. This is an assertion that the caller passed a local-access instruction.

Solutions

  1. Before calling LocalIndex(), check the opcode with code.IsLdloc() / code.IsStloc() (or a switch over OpCodes.Ldloc*, Ldloca*, Stloc*) and skip non-local instructions
  2. Restrict the matching pattern in your transpiler (CodeMatch with the ldloc/stloc opcodes) so LocalIndex() is only reached for real local accesses
  3. If the intent was an argument index, use ArgumentIndex() instead; if a general operand index is needed, guard with code.operand is LocalBuilder or int

Example fix

// before
var idx = instr.LocalIndex();
// after
if (instr.IsLdloc() || instr.IsStloc())
    var idx = instr.LocalIndex();
Defensive patterns

Strategy: validation

Validate before calling

static int? TryLocalIndex(CodeInstruction code) =>
    code.IsLdloc() || code.IsStloc() ? code.LocalIndex() : null;

Type guard

static bool IsLocalAccess(CodeInstruction c) =>
    c.opcode == OpCodes.Ldloc || c.opcode == OpCodes.Ldloc_S ||
    c.opcode == OpCodes.Ldloca || c.opcode == OpCodes.Ldloca_S ||
    c.opcode == OpCodes.Stloc || c.opcode == OpCodes.Stloc_S;

Try / catch

int idx;
try { idx = instr.LocalIndex(); }
catch (ArgumentException) { continue; // not a local access instruction }

Prevention

When it happens

Trigger: Calling CodeInstruction.LocalIndex() on an instruction whose opcode is not Ldloc/Ldloc_S/Ldloca/Ldloca_S/Stloc/Stloc_S, e.g. a call, br, or ldarg instruction, typically inside an IL transpiler loop that assumed all instructions in a range are local accesses.

Common situations: Transpiler authors scanning a method body and blindly calling LocalIndex() on every instruction; cursor/emitter code filtering locals after a refactor changed which instructions are matched; assumptions broken by compiler output differences (Debug vs Release IL) that changed opcode sequences.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of pardeike/Harmony@e7872dc170 (2026-09-15). Data as JSON: /api/errors/6a81b6c9a126c86f. Report an issue: GitHub.

Appendix: source

Thrown at Harmony/Tools/Extensions.cs:650

			{
				if (code.operand is LocalBuilder localBuilder)
					return localBuilder.LocalIndex;
				return Convert.ToInt32(code.operand);
			}
			else if (code.opcode == OpCodes.Stloc_S || code.opcode == OpCodes.Stloc)
			{
				if (code.operand is LocalBuilder localBuilder)
					return localBuilder.LocalIndex;
				return Convert.ToInt32(code.operand);
			}
			else if (code.opcode == OpCodes.Ldloca_S || code.opcode == OpCodes.Ldloca)
			{
				if (code.operand is LocalBuilder localBuilder)
					return localBuilder.LocalIndex;
				return Convert.ToInt32(code.operand);
			}
			else
				throw new ArgumentException("Instruction is not a load or store", nameof(code));
		}

		/// <summary>Returns the index targeted by this <c>ldarg</c>, <c>ldarga</c>, or <c>starg</c></summary>
		/// <param name="code">The <see cref="CodeInstruction"/></param>
		/// <returns>The index it targets</returns>
		/// <seealso cref="CodeInstruction.LoadArgument(int, bool)"/>
		/// <seealso cref="CodeInstruction.StoreArgument(int)"/>
		public static int ArgumentIndex(this CodeInstruction code)
		{
			if (code.opcode == OpCodes.Ldarg_0)
				return 0;
			else if (code.opcode == OpCodes.Ldarg_1)
				return 1;
			else if (code.opcode == OpCodes.Ldarg_2)
				return 2;
			else if (code.opcode == OpCodes.Ldarg_3)
				return 3;
			else if (code.opcode == OpCodes.Ldarg_S || code.opcode == OpCodes.Ldarg)

View on GitHub (pinned to e7872dc170)