diff --git a/Source/Parser/Expressions/IntegerConstantExpression.cs b/Source/Parser/Expressions/IntegerConstantExpression.cs index 0af38c40..08eff611 100644 --- a/Source/Parser/Expressions/IntegerConstantExpression.cs +++ b/Source/Parser/Expressions/IntegerConstantExpression.cs @@ -44,7 +44,7 @@ public bool IsZero /// /// Returns true if the constant is numerically negative /// - public bool IsNegative + public virtual bool IsNegative { get { return Value < 0; } } @@ -52,7 +52,7 @@ public bool IsNegative /// /// Returns true if the constant is numerically positive /// - public bool IsPositive + public virtual bool IsPositive { get { return Value > 0; } } @@ -91,36 +91,52 @@ public ExpressionBase Combine(ExpressionBase right, MathematicOperation operatio var integerExpression = right as IntegerConstantExpression; if (integerExpression != null) { + var newValue = 0; switch (operation) { case MathematicOperation.Add: - return new IntegerConstantExpression(Value + integerExpression.Value); + newValue = Value + integerExpression.Value; + break; case MathematicOperation.Subtract: - return new IntegerConstantExpression(Value - integerExpression.Value); + newValue = Value - integerExpression.Value; + break; case MathematicOperation.Multiply: - return new IntegerConstantExpression(Value * integerExpression.Value); + newValue = Value * integerExpression.Value; + break; case MathematicOperation.Divide: if (integerExpression.Value == 0) return new ErrorExpression("Division by zero"); - return new IntegerConstantExpression(Value / integerExpression.Value); + newValue = Value / integerExpression.Value; + break; case MathematicOperation.Modulus: if (integerExpression.Value == 0) return new ErrorExpression("Division by zero"); - return new IntegerConstantExpression(Value % integerExpression.Value); + newValue = Value % integerExpression.Value; + break; case MathematicOperation.BitwiseAnd: - return new IntegerConstantExpression(Value & integerExpression.Value); + newValue = Value & integerExpression.Value; + break; case MathematicOperation.BitwiseXor: - return new IntegerConstantExpression(Value ^ integerExpression.Value); + newValue = Value ^ integerExpression.Value; + break; default: break; } + + if (right is UnsignedIntegerConstantExpression || + this is UnsignedIntegerConstantExpression) + { + return new UnsignedIntegerConstantExpression((uint)newValue); + } + + return new IntegerConstantExpression(newValue); } if (right is FloatConstantExpression) @@ -182,4 +198,37 @@ public ExpressionBase NormalizeComparison(ExpressionBase right, ComparisonOperat return null; } } + + internal class UnsignedIntegerConstantExpression : IntegerConstantExpression + { + public UnsignedIntegerConstantExpression(uint value) + : base((int)value) + { + } + + /// + /// Returns true if the constant is numerically negative + /// + public override bool IsNegative + { + get { return false; } + } + + /// + /// Returns true if the constant is numerically positive + /// + public override bool IsPositive + { + get { return Value != 0; } + } + + /// + /// Appends the textual representation of this expression to . + /// + internal override void AppendString(StringBuilder builder) + { + builder.Append((uint)Value); + builder.Append('U'); + } + } } diff --git a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs index 7a9cd505..d4f2305a 100644 --- a/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs +++ b/Source/Parser/Expressions/Trigger/MemoryValueExpression.cs @@ -548,7 +548,7 @@ private ExpressionBase EnsureSingleExpressionOnRightHandSide(ExpressionBase righ return new ComparisonExpression(cloneLeft, operation, constant); } - private MemoryValueExpression InvertAndMigrateAccessorsTo(ExpressionBase target) + internal MemoryValueExpression InvertAndMigrateAccessorsTo(ExpressionBase target) { var value = target as MemoryValueExpression; if (value == null) @@ -1004,6 +1004,7 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp { Debug.Assert(comparison.Left is MemoryValueExpression); var leftMemoryValue = (MemoryValueExpression)comparison.Left; + IntegerConstantExpression integerConstant; var newLeft = leftMemoryValue.Clone(); newLeft.IntegerConstant += underflowAdjustment; @@ -1036,7 +1037,7 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp else if (underflowAdjustment < 0) { // attempting to clamp adjustment, see if we can clamp it even more - var integerConstant = newRight as IntegerConstantExpression; + integerConstant = newRight as IntegerConstantExpression; if (integerConstant != null) { var newConstant = integerConstant.Value - newLeft.IntegerConstant; @@ -1049,6 +1050,12 @@ private static ExpressionBase ApplyUnderflowAdjustment(ComparisonExpression comp } } + // if the resulting underflow comparison target is negative, explicitly make it positive + // to prevent further underflow calculations. + integerConstant = newRight as IntegerConstantExpression; + if (integerConstant != null && integerConstant.IsNegative) + newRight = new UnsignedIntegerConstantExpression((uint)integerConstant.Value); + return new ComparisonExpression(newLeft, operation, newRight); } @@ -1063,6 +1070,9 @@ private int GetUnderflowAdjustment(ExpressionBase right) switch (right.Type) { case ExpressionType.IntegerConstant: + if (right is UnsignedIntegerConstantExpression) + return 0; // explicit unsigned in comparison, do not adjust for underflow + integerOffset = ((IntegerConstantExpression)right).Value; break; diff --git a/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs b/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs index 3e568300..10a4f7db 100644 --- a/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs +++ b/Source/Parser/Expressions/Trigger/ModifiedMemoryAccessorExpression.cs @@ -700,7 +700,50 @@ public ExpressionBase NormalizeComparison(ExpressionBase right, ComparisonOperat result = newRight.ApplyMathematic(modifier, opposingOperator); if (result is MathematicExpression) - return new ErrorExpression("Result can never be true using integer math"); + { + var zero = new UnsignedIntegerConstantExpression(0U); + var negative = new UnsignedIntegerConstantExpression(0x80000000U); + + // left and right are both modified memory expressions. use subsource and check the negative bit + var memoryValue = new MemoryValueExpression(this); + memoryValue.ApplyMathematic(modifiedMemoryAccessor.Clone(), MathematicOperation.Subtract); + switch (operation) + { + case ComparisonOperation.Equal: + case ComparisonOperation.NotEqual: + // A * 3 == B * 4 => A * 3 - B * 4 == 0 => A * 3 - B * 4 == 0 + // A * 3 != B * 4 => A * 3 - B * 4 != 0 => A * 3 - B * 4 != 0 + return new ComparisonExpression(memoryValue, operation, zero); + + case ComparisonOperation.LessThan: + case ComparisonOperation.GreaterThanOrEqual: + // A * 3 < B * 4 => A * 3 - B * 4 < 0 => A * 3 - B * 4 >= 0x80000000 + // A * 3 >= B * 4 => A * 3 - B * 4 >= 0 => A * 3 - B * 4 < 0x80000000 + var newOperation = ComparisonExpression.GetOppositeComparisonOperation(operation); + return new ComparisonExpression(memoryValue, newOperation, negative); + + case ComparisonOperation.LessThanOrEqual: + // A * 3 <= B * 4 => A * 3 - B * 4 <= 0 => A * 3 - B * 4 >= 0x80000000 || == 0 + var negativeComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.GreaterThanOrEqual, Right = negative }; + var zeroComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.Equal, Right = zero }; + var lessThanOrEqualClause = new RequirementClauseExpression() { Operation = ConditionalOperation.Or }; + lessThanOrEqualClause.AddCondition(negativeComparison); + lessThanOrEqualClause.AddCondition(zeroComparison); + return lessThanOrEqualClause; + + case ComparisonOperation.GreaterThan: + // A * 3 > B * 4 => A * 3 - B * 4 > 0 => A * 3 - B * 4 < 0x80000000 && != 0 + var positiveComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.LessThan, Right = negative }; + var nonZeroComparison = new RequirementConditionExpression() { Left = memoryValue, Comparison = ComparisonOperation.NotEqual, Right = zero }; + var greaterThanClause = new RequirementClauseExpression() { Operation = ConditionalOperation.And }; + greaterThanClause.AddCondition(positiveComparison); + greaterThanClause.AddCondition(nonZeroComparison); + return greaterThanClause; + + default: + return new ErrorExpression("Result can never be true using integer math"); + } + } // swap so modifier is on left newRight = newLeft; diff --git a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs index fe11f6a5..42fcc684 100644 --- a/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs +++ b/Source/Parser/Expressions/Trigger/RequirementConditionExpression.cs @@ -57,7 +57,7 @@ internal override void AppendString(StringBuilder builder) // special handling: comparisons use unsigned values var rightInteger = Right as IntegerConstantExpression; - if (rightInteger != null && rightInteger.Value < 0) + if (rightInteger != null && rightInteger.IsNegative) builder.Append((uint)rightInteger.Value); else Right.AppendString(builder); @@ -419,19 +419,26 @@ private static void NormalizeLimits(ref RequirementExpressionBase expression) } else if (value > max) { - switch (condition.Comparison) + if (value > Int32.MaxValue && min < 0) { - case ComparisonOperation.GreaterThan: - case ComparisonOperation.GreaterThanOrEqual: - case ComparisonOperation.Equal: - expression = new AlwaysFalseExpression(); - return; + // value is implicitly negative, ignore it. + } + else + { + switch (condition.Comparison) + { + case ComparisonOperation.GreaterThan: + case ComparisonOperation.GreaterThanOrEqual: + case ComparisonOperation.Equal: + expression = new AlwaysFalseExpression(); + return; - case ComparisonOperation.LessThan: - case ComparisonOperation.LessThanOrEqual: - case ComparisonOperation.NotEqual: - expression = new AlwaysTrueExpression(); - return; + case ComparisonOperation.LessThan: + case ComparisonOperation.LessThanOrEqual: + case ComparisonOperation.NotEqual: + expression = new AlwaysTrueExpression(); + return; + } } } else if (value == max) @@ -504,6 +511,26 @@ public ExpressionBase Normalize() return reversed.Normalize(); } + var integerRight = Right as IntegerConstantExpression; + if (integerRight != null && integerRight.IsNegative && integerRight.Value > -100000) + { + var memoryValue = Left as MemoryValueExpression; + if (memoryValue != null && memoryValue.MemoryAccessors.Any(a => a.CombiningOperator == RequirementType.SubSource)) + { + // A - B < -2 => B - A > 2 + var newMemoryValue = new MemoryValueExpression(); + newMemoryValue = memoryValue.InvertAndMigrateAccessorsTo(newMemoryValue); + var integerConstant = new IntegerConstantExpression(-integerRight.Value); + var normalized = new RequirementConditionExpression + { + Left = newMemoryValue, + Comparison = ComparisonExpression.ReverseComparisonOperation(Comparison), + Right = integerConstant + }; + return normalized.Normalize(); + } + } + var modifiedMemoryAccessor = Left as ModifiedMemoryAccessorExpression; if (modifiedMemoryAccessor != null && modifiedMemoryAccessor.ModifyingOperator != RequirementOperator.None) { diff --git a/Source/Parser/Internal/ExpressionBase.cs b/Source/Parser/Internal/ExpressionBase.cs index c005d426..bb840d3b 100644 --- a/Source/Parser/Internal/ExpressionBase.cs +++ b/Source/Parser/Internal/ExpressionBase.cs @@ -480,8 +480,15 @@ private static ExpressionBase ParseNumber(PositionalTokenizer tokenizer, bool is return new ErrorExpression("Number too large"); } - if (value > Int32.MaxValue && !isUnsigned) - return new ErrorExpression("Number too large"); + if (value > Int32.MaxValue) + { + if (!isUnsigned) + return new ErrorExpression("Number too large"); + + var unsignedIntegerExpression = new UnsignedIntegerConstantExpression(value); + unsignedIntegerExpression.Location = new TextRange(line, column, endLine, endColumn); + return unsignedIntegerExpression; + } var integerExpression = new IntegerConstantExpression((int)value); integerExpression.Location = new TextRange(line, column, endLine, endColumn); diff --git a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs index d1250ac9..fdcab6fa 100644 --- a/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs +++ b/Tests/Parser/Expressions/Trigger/ModifiedMemoryAcessorExpressionTests.cs @@ -152,8 +152,6 @@ public void TestCombine(string left, string operation, string right, ExpressionT ExpressionType.Comparison, "float(0x001234) != 3.363636")] [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 5", ExpressionType.Comparison, "byte(0x001234) * 2 == byte(0x002345)")] - [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 3", - ExpressionType.Error, "Result can never be true using integer math")] [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) / 3", ExpressionType.Comparison, "byte(0x001234) * 30 == byte(0x002345)")] [TestCase("byte(0x001234) / 10", "=", "byte(0x002345) * 3", @@ -178,6 +176,18 @@ public void TestCombine(string left, string operation, string right, ExpressionT ExpressionType.None, null)] // division cannot be moved; multiplication cannot be extracted [TestCase("byte(0x001234) / byte(0x002345) * 10", ">", "80", ExpressionType.Comparison, "byte(0x001234) / byte(0x002345) > 8")] + [TestCase("byte(0x001234) * 10", "=", "byte(0x002345) * 8", + ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 == 0U")] // comparison of two modified memory accesors results in subsource chain + [TestCase("byte(0x001234) * 10", "!=", "byte(0x002345) * 8", + ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 != 0U")] // comparison of two modified memory accesors results in subsource chain + [TestCase("byte(0x001234) * 10", "<", "byte(0x002345) * 8", + ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 >= 2147483648U")] // comparison of two modified memory accesors results in subsource chain + [TestCase("byte(0x001234) * 10", "<=", "byte(0x002345) * 8", + ExpressionType.Requirement, "byte(0x001234) * 10 - byte(0x002345) * 8 >= 2147483648U || byte(0x001234) * 10 - byte(0x002345) * 8 == 0U")] // comparison of two modified memory accesors results in subsource chain + [TestCase("byte(0x001234) * 10", ">", "byte(0x002345) * 8", + ExpressionType.Requirement, "byte(0x001234) * 10 - byte(0x002345) * 8 < 2147483648U && byte(0x001234) * 10 - byte(0x002345) * 8 != 0U")] // comparison of two modified memory accesors results in subsource chain + [TestCase("byte(0x001234) * 10", ">=", "byte(0x002345) * 8", + ExpressionType.Comparison, "byte(0x001234) * 10 - byte(0x002345) * 8 < 2147483648U")] // comparison of two modified memory accesors results in subsource chain public void TestNormalizeComparison(string left, string operation, string right, ExpressionType expectedType, string expected) { ExpressionTests.AssertNormalizeComparison(left, operation, right, expectedType, expected); diff --git a/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs b/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs index 1ad3b66d..5fcb2154 100644 --- a/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs +++ b/Tests/Parser/Expressions/Trigger/RequirementClauseExpression_Tests.cs @@ -1,11 +1,9 @@ using Jamiras.Components; using NUnit.Framework; -using RATools.Data; using RATools.Parser; using RATools.Parser.Expressions; using RATools.Parser.Expressions.Trigger; using RATools.Parser.Internal; -using System.Collections.Generic; namespace RATools.Tests.Parser.Expressions.Trigger { diff --git a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs index 17a81d3d..9551428f 100644 --- a/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs +++ b/Tests/Parser/Expressions/Trigger/RequirementConditionExpression_Tests.cs @@ -54,6 +54,11 @@ public void TestAppendString(string input) [TestCase("low4(0x001234) * 10000000 + byte(0x1235) != prev(low4(0x002345)) * 10000000 + prev(byte(0x2346))", "A:0xL001234*10000000_B:d0xL002345*10000000_0xH001235!=d0xH002346")] // don't need underflow adjustment for inequality [TestCase("dword(dword(0x001234) + 8) - dword(dword(0x001234) + 12) > 100000", "A:100000_I:0xX001234_0xX00000c<0xX000008")] // AddAddress can be shared [TestCase("dword(dword(0x001234) + 8) - dword(dword(0x001234) + 12) * 4 > 100000", "I:0xX001234_B:0xX00000c*4_I:0xX001234_0xX000008>100000")] // AddAddress cannot be shared + [TestCase("word(0x18294A) * 10 - word(0x182946) * 8 < 0x80000000", "A:0x 18294a*10_B:0x 182946*8_0<2147483648")] + [TestCase("byte(0x1234) - byte(0x2345) == 1", "B:0xH002345=0_0xH001234=1")] + [TestCase("byte(0x1234) - byte(0x2345) == -1", "B:0xH001234=0_0xH002345=1")] // invert to eliminate negative value + [TestCase("byte(0x1234) - byte(0x2345) == 4294967295", "B:0xH002345=0_0xH001234=4294967295")] // don't invert very high positive value + [TestCase("byte(0x1234) - byte(0x2345) <= -1", "A:1=0_0xH001234<=0xH002345")] public void TestBuildTrigger(string input, string expected) { var clause = TriggerExpressionTests.Parse(input); @@ -85,12 +90,12 @@ public void TestBuildTrigger(string input, string expected) [TestCase("tbyte(0x001234) <= 16777215", "always_true()")] [TestCase("tbyte(0x001234) > 16777215", "always_false()")] [TestCase("tbyte(0x001234) >= 16777215", "tbyte(0x001234) == 16777215")] - [TestCase("dword(0x001234) == 4294967295", "dword(0x001234) == 4294967295")] - [TestCase("dword(0x001234) != 4294967295", "dword(0x001234) != 4294967295")] - [TestCase("dword(0x001234) < 4294967295", "dword(0x001234) < 4294967295")] + [TestCase("dword(0x001234) == 4294967295", "dword(0x001234) == 4294967295U")] + [TestCase("dword(0x001234) != 4294967295", "dword(0x001234) != 4294967295U")] + [TestCase("dword(0x001234) < 4294967295", "dword(0x001234) < 4294967295U")] [TestCase("dword(0x001234) <= 4294967295", "always_true()")] [TestCase("dword(0x001234) > 4294967295", "always_false()")] - [TestCase("dword(0x001234) >= 4294967295", "dword(0x001234) == 4294967295")] + [TestCase("dword(0x001234) >= 4294967295", "dword(0x001234) == 4294967295U")] [TestCase("bit3(0x001234) == 0", "bit3(0x001234) == 0")] [TestCase("bit3(0x001234) != 0", "bit3(0x001234) == 1")] [TestCase("bit3(0x001234) < 0", "always_false()")]