Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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