Fix IcedAssembler immediate overflow and unwrap invoke exceptions
- TryBind wraps Convert.ChangeType in TryChangeType so an immediate that overflows a candidate parameter type returns false (letting a wider overload be tried) instead of throwing OverflowException out of assembly. Verified: "mov eax, 4294967295" and "mov eax, -2147483649" no longer crash. - Immediate now carries a boxed long OR ulong; TryParseImmediate parses decimal values above long.MaxValue via a ulong fallback, and hex via ulong. Previously such literals were rejected at parse time. - Unwrap TargetInvocationException from method.Invoke so callers see the real Iced failure, not the reflection wrapper. Tests: 231 passing, 4 skipped. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -128,8 +128,17 @@ public sealed class IcedAssembler : IAssembler
|
||||
continue;
|
||||
|
||||
if (TryBind(parameters, operands, out object?[]? boundArgs))
|
||||
{
|
||||
try
|
||||
{
|
||||
method.Invoke(assembler, boundArgs);
|
||||
}
|
||||
catch (TargetInvocationException ex) when (ex.InnerException is not null)
|
||||
{
|
||||
// Surface the real Iced failure rather than the reflection wrapper.
|
||||
throw ex.InnerException;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -151,7 +160,16 @@ public sealed class IcedAssembler : IAssembler
|
||||
switch (operand)
|
||||
{
|
||||
case Immediate imm when IsNumeric(paramType):
|
||||
args[i] = Convert.ChangeType(imm.Value, paramType, CultureInfo.InvariantCulture);
|
||||
// An immediate that overflows this parameter's type means this overload
|
||||
// is the wrong width; return false so a wider overload can be tried
|
||||
// instead of crashing the whole assembly.
|
||||
if (!TryChangeType(imm.Value, paramType, out object? converted))
|
||||
{
|
||||
boundArgs = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
args[i] = converted;
|
||||
break;
|
||||
|
||||
case not null when paramType.IsInstanceOfType(operand):
|
||||
@@ -178,28 +196,63 @@ public sealed class IcedAssembler : IAssembler
|
||||
if (labels.TryGetValue(token, out Label label))
|
||||
return label;
|
||||
|
||||
if (TryParseImmediate(token, out long value))
|
||||
return new Immediate(value);
|
||||
if (TryParseImmediate(token, out object? value))
|
||||
return new Immediate(value!);
|
||||
|
||||
throw new NotSupportedException(
|
||||
$"Unrecognized operand '{token}' (expected a register, an immediate, or a label).");
|
||||
}
|
||||
|
||||
private static bool TryParseImmediate(string token, out long value)
|
||||
// Parses an immediate as the narrowest of long/ulong that holds it, boxed. Storing the
|
||||
// widest representation lets TryChangeType later narrow it to whatever integer parameter
|
||||
// the chosen overload expects — and reject (rather than crash on) values that do not fit.
|
||||
private static bool TryParseImmediate(string token, out object? value)
|
||||
{
|
||||
value = null;
|
||||
bool negative = token.StartsWith('-');
|
||||
string body = negative ? token[1..] : token;
|
||||
|
||||
bool ok;
|
||||
if (body.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
|
||||
ok = long.TryParse(body[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out value);
|
||||
{
|
||||
if (!ulong.TryParse(body[2..], NumberStyles.HexNumber, CultureInfo.InvariantCulture, out ulong hex))
|
||||
return false;
|
||||
|
||||
value = negative ? -(long)hex : hex;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (negative)
|
||||
{
|
||||
if (!long.TryParse(token, NumberStyles.Integer, CultureInfo.InvariantCulture, out long signed))
|
||||
return false;
|
||||
|
||||
value = signed;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-negative decimal: prefer long, fall back to ulong for values above long.MaxValue.
|
||||
if (long.TryParse(body, NumberStyles.Integer, CultureInfo.InvariantCulture, out long asLong))
|
||||
value = asLong;
|
||||
else if (ulong.TryParse(body, NumberStyles.Integer, CultureInfo.InvariantCulture, out ulong asULong))
|
||||
value = asULong;
|
||||
else
|
||||
ok = long.TryParse(body, NumberStyles.Integer, CultureInfo.InvariantCulture, out value);
|
||||
return false;
|
||||
|
||||
if (ok && negative)
|
||||
value = -value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return ok;
|
||||
private static bool TryChangeType(object value, Type targetType, out object? result)
|
||||
{
|
||||
try
|
||||
{
|
||||
result = Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is OverflowException or InvalidCastException or FormatException)
|
||||
{
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsNumeric(Type type) => Type.GetTypeCode(type) is
|
||||
@@ -266,9 +319,10 @@ public sealed class IcedAssembler : IAssembler
|
||||
return map;
|
||||
}
|
||||
|
||||
// A parsed immediate, distinguished from register/label operands so binding can widen
|
||||
// or narrow it to whichever integer parameter type the chosen overload expects.
|
||||
private readonly record struct Immediate(long Value);
|
||||
// A parsed immediate (boxed long or ulong), distinguished from register/label operands
|
||||
// so binding can narrow it to whichever integer parameter type the chosen overload
|
||||
// expects — or reject it when it does not fit.
|
||||
private readonly record struct Immediate(object Value);
|
||||
|
||||
private sealed class ByteListCodeWriter : CodeWriter
|
||||
{
|
||||
|
||||
@@ -82,6 +82,32 @@ public class IcedAssemblerTests
|
||||
Assert.Equal(expected, jmp.NearBranchTarget);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("mov eax, 4294967295")] // 0xFFFFFFFF — needs the uint overload, not int
|
||||
[InlineData("mov eax, 0xFFFFFFFF")] // same value, hex form
|
||||
[InlineData("mov rax, 18446744073709551615")] // ulong.MaxValue — decimal above long.MaxValue
|
||||
public void Assemble_binds_wide_unsigned_immediates(string source)
|
||||
{
|
||||
var assembler = new IcedAssembler(64);
|
||||
|
||||
byte[] code = assembler.Assemble(source);
|
||||
Assert.NotEmpty(code);
|
||||
|
||||
Instruction[] instructions = Disassemble(code, 64, 0);
|
||||
Assert.Single(instructions);
|
||||
Assert.Equal(Mnemonic.Mov, instructions[0].Mnemonic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_rejects_immediate_that_fits_no_overload_without_crashing()
|
||||
{
|
||||
var assembler = new IcedAssembler(64);
|
||||
|
||||
// -2147483649 is below int.MinValue and eax has no wider signed overload; must be a
|
||||
// clean NotSupportedException, not an OverflowException escaping from ChangeType.
|
||||
Assert.Throws<NotSupportedException>(() => assembler.Assemble("mov eax, -2147483649"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Assemble_throws_on_unsupported_operand()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user