Skip to content
95 changes: 95 additions & 0 deletions src/Neo.Compiler.CSharp/MethodConvert/Helpers/ConvertHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ private bool TryProcessInlineMethods(SemanticModel model, IMethodSymbol symbol,
&& (MethodImplOptions)attribute.ConstructorArguments[0].Value! == MethodImplOptions.AggressiveInlining))
return false;

// Validation 1: Check for ref/out parameters
if (symbol.Parameters.Any(p => p.RefKind != RefKind.None))
{
throw new CompilationException(symbol, DiagnosticId.SyntaxNotSupported,
$"Cannot inline method '{symbol.Name}': Methods with ref/out parameters cannot be inlined.");
}

// Validation 2: Check for recursive calls
if (IsRecursiveMethod(syntax, symbol))
{
throw new CompilationException(symbol, DiagnosticId.SyntaxNotSupported,
$"Cannot inline method '{symbol.Name}': Recursive methods cannot be inlined.");
}

// Validation 3: Check method size (optional warning for large methods)
var methodSize = EstimateMethodSize(syntax);
if (methodSize > 50) // Threshold for "large" methods
{
// This is a warning, not an error - we still allow it but warn the user
System.Console.WriteLine($"Warning: Inlining large method '{symbol.Name}' ({methodSize} estimated instructions). This may increase contract size significantly.");
}

_internalInline = true;

using (InsertSequencePoint(syntax))
Expand Down Expand Up @@ -101,6 +123,79 @@ private void ValidateMethodName()
throw new CompilationException(Symbol, DiagnosticId.InvalidMethodName, $"The method name {Symbol.Name} is not valid.");
}

/// <summary>
/// Checks if a method contains recursive calls to itself
/// </summary>
private bool IsRecursiveMethod(BaseMethodDeclarationSyntax syntax, IMethodSymbol symbol)
{
// Check method body for recursive calls
if (syntax.Body != null)
{
var invocations = syntax.Body.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
if (invocation.Expression is IdentifierNameSyntax identifier &&
identifier.Identifier.Text == symbol.Name)
{
return true;
}
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
memberAccess.Name.Identifier.Text == symbol.Name)
{
return true;
}
}
}

// Check expression body for recursive calls
if (syntax.ExpressionBody != null)
{
var invocations = syntax.ExpressionBody.DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocation in invocations)
{
if (invocation.Expression is IdentifierNameSyntax identifier &&
identifier.Identifier.Text == symbol.Name)
{
return true;
}
if (invocation.Expression is MemberAccessExpressionSyntax memberAccess &&
memberAccess.Name.Identifier.Text == symbol.Name)
{
return true;
}
}
}

return false;
}

/// <summary>
/// Estimates the size of a method in terms of approximate instruction count
/// </summary>
private int EstimateMethodSize(BaseMethodDeclarationSyntax syntax)
{
int size = 0;

// Count nodes in method body
if (syntax.Body != null)
{
// Each statement roughly translates to 1-3 instructions
size += syntax.Body.Statements.Count * 2;

// Each expression adds complexity
size += syntax.Body.DescendantNodes().OfType<ExpressionSyntax>().Count();
}

// Count nodes in expression body
if (syntax.ExpressionBody != null)
{
// Expression bodies are typically smaller
size += syntax.ExpressionBody.DescendantNodes().Count();
}

return size;
}

private void InsertInitializationInstructions()
{
if (Symbol.MethodKind == MethodKind.StaticConstructor && _context.StaticFieldCount > 0)
Expand Down
190 changes: 190 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_Inline_EdgeCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using System.Runtime.CompilerServices;
using Neo.SmartContract.Framework;

namespace Neo.Compiler.CSharp.TestContracts
{
public class Contract_Inline_EdgeCases : SmartContract.Framework.SmartContract
{
// Test 1: Parameter shadowing issue - inline method parameter might shadow caller's variable
public static int TestParameterShadowing()
{
int value = 10;
int result = InlineWithSameParamName(5);
// If inlining is broken, 'value' might be incorrectly modified
return value + result; // Should return 10 + 5 = 15
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineWithSameParamName(int value)
{
return value; // This 'value' should be the parameter, not the caller's variable
}

// Test 2: Multiple calls to same inline method
public static int TestMultipleCalls()
{
int a = InlineAdd(1, 2); // 3
int b = InlineAdd(3, 4); // 7
int c = InlineAdd(a, b); // 10
return c;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineAdd(int x, int y)
{
return x + y;
}

// Test 3: Inline method with local variables
public static int TestLocalVariables()
{
return InlineWithLocals(5, 3);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineWithLocals(int a, int b)
{
int temp1 = a * 2; // 10
int temp2 = b * 3; // 9
int result = temp1 + temp2; // 19
return result;
}

// Test 4: Nested inline calls
public static int TestNestedInline()
{
return InlineOuter(5);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineOuter(int x)
{
return InlineInner(x * 2);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineInner(int y)
{
return y + 1;
}

// Test 5: Inline with conditional logic
public static int TestConditionalInline(bool flag)
{
return InlineConditional(flag, 10, 20);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineConditional(bool condition, int a, int b)
{
if (condition)
return a;
else
return b;
}

// Test 6: Inline void method with side effects
private static int counter = 0;

public static int TestVoidInline()
{
InlineVoidMethod();
InlineVoidMethod();
return counter; // Should be 2
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InlineVoidMethod()
{
counter++;
}

// Test 7: Inline with expression body that returns value in void context
public static void TestExpressionBodyVoid()
{
InlineExpressionVoid(5);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InlineExpressionVoid(int x) => counter = x + 1;

// Test 8: Inline with expression body that returns value
public static int TestExpressionBodyReturn()
{
return InlineExpressionReturn(7, 3);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineExpressionReturn(int a, int b) => a * b;

// Test 9: Parameter order with different calling conventions
public static int TestParameterOrder()
{
return InlineParamOrder(1, 2, 3, 4);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineParamOrder(int a, int b, int c, int d)
{
return a * 1000 + b * 100 + c * 10 + d; // Should return 1234
}

// Test 10: Inline with out parameters (should this even work?)
public static int TestOutParameter()
{
InlineWithOut(5, out int result);
return result;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InlineWithOut(int input, out int output)
{
output = input * 2;
}

// Test 11: Recursive inline (should probably not inline)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineRecursive(int n)
{
if (n <= 1) return 1;
return n * InlineRecursive(n - 1); // Recursive call
}

public static int TestRecursiveInline()
{
return InlineRecursive(5); // Should return 120 (5!)
}

// Test 12: Inline method calling non-inline method
public static int TestInlineCallingNonInline()
{
return InlineWrapper(10);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineWrapper(int x)
{
return NonInlineMethod(x);
}

private static int NonInlineMethod(int y)
{
return y * 2;
}

// Test 13: Check stack handling with complex expressions
public static int TestComplexExpression()
{
int x = 5;
int y = 10;
// Complex expression with multiple inline calls
return InlineAdd(InlineMultiply(x, 2), InlineAdd(y, InlineMultiply(3, 4)));
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int InlineMultiply(int a, int b)
{
return a * b;
}
}
}
82 changes: 82 additions & 0 deletions tests/Neo.Compiler.CSharp.TestContracts/Contract_Inline_Invalid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
using System.Runtime.CompilerServices;
using Neo.SmartContract.Framework;

namespace Neo.Compiler.CSharp.TestContracts
{
public class Contract_Inline_Invalid : SmartContract.Framework.SmartContract
{
// Test 1: Recursive inline method - should fail compilation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int RecursiveInline(int n)
{
if (n <= 1) return 1;
return n * RecursiveInline(n - 1); // Recursive call should trigger error
}

// Test 2: Inline method with out parameter - should fail compilation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InlineWithOut(int input, out int output)
{
output = input * 2;
}

// Test 3: Inline method with ref parameter - should fail compilation
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InlineWithRef(ref int value)
{
value *= 2;
}

// Test 4: Large inline method - should generate warning but compile
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int LargeInlineMethod(int x)
{
// Artificially large method with many operations
int result = x;
result = result * 2 + 1;
result = result * 3 + 2;
result = result * 4 + 3;
result = result * 5 + 4;
result = result * 6 + 5;
result = result * 7 + 6;
result = result * 8 + 7;
result = result * 9 + 8;
result = result * 10 + 9;
result = result * 11 + 10;
result = result * 12 + 11;
result = result * 13 + 12;
result = result * 14 + 13;
result = result * 15 + 14;
result = result * 16 + 15;
result = result * 17 + 16;
result = result * 18 + 17;
result = result * 19 + 18;
result = result * 20 + 19;
return result;
}

// Entry points for testing
public static int TestRecursive()
{
return RecursiveInline(5);
}

public static int TestOut()
{
InlineWithOut(5, out int result);
return result;
}

public static int TestRef()
{
int value = 5;
InlineWithRef(ref value);
return value;
}

public static int TestLarge()
{
return LargeInlineMethod(1);
}
}
}
Loading
Loading