Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix Biginteger default. #1242

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;

namespace Neo.Compiler;
Expand Down Expand Up @@ -99,7 +100,19 @@ private bool TryOptimizedObjectCreation(SemanticModel model, BaseObjectCreationE
if (indexToValue.TryGetValue(i, out ExpressionSyntax? right))
ConvertExpression(model, right);
else
PushDefault(fields[i].Type);
{
// The default BigInteger is null.
// But object creation will assign BigInteger a default value
if (fields[i].Type.Name == nameof(BigInteger))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better to check types with namespace

{
AddInstruction(OpCode.PUSH0);
}
else
{
PushDefault(fields[i].Type);
}
}

Push(fields.Length + needVirtualMethodTable);
AddInstruction(type.IsValueType || type.IsRecord ? OpCode.PACKSTRUCT : OpCode.PACK);
return true;
Expand Down
14 changes: 13 additions & 1 deletion src/Neo.Compiler.CSharp/MethodConvert/Helpers/ConvertHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Runtime.CompilerServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
Expand Down Expand Up @@ -199,8 +200,19 @@ private void InitializeFieldForObject(SemanticModel model, IFieldSymbol field, I
}
}
}

if (expression is null)
PushDefault(field.Type);
{
// Object creation will assign BigInteger a default value.
if (field.Type.Name == nameof(BigInteger))
{
AddInstruction(OpCode.PUSH0);
}
else
{
PushDefault(field.Type);
}
}
else
ConvertExpression(model, expression);
}
Expand Down
7 changes: 7 additions & 0 deletions src/Neo.Compiler.CSharp/MethodConvert/Helpers/StackHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ private void Push(object? obj)

private Instruction PushDefault(ITypeSymbol type)
{
// Among all integer types, BigInteger has no default value.
// To manage this, we will enforce all BigInteger be initialized while defined in Analyzer.
if (type.Name == nameof(BigInteger))
{
return AddInstruction(OpCode.PUSHNULL);
}

return AddInstruction(type.GetStackItemType() switch
{// https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/default-values
VM.Types.StackItemType.Boolean => OpCode.PUSHF,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
### New Rules

| Rule ID | Category | Severity | Notes |

Check warning on line 3 in src/Neo.SmartContract.Analyzer/AnalyzerReleases.Unshipped.md

View workflow job for this annotation

GitHub Actions / Test

Analyzer release file 'AnalyzerReleases.Unshipped.md' has a missing or invalid release header '| Rule ID | Category | Severity | Notes |' (https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md)
|---------|----------|----------|--------------------------------------------|
| NC4002 | Type | Error | FloatUsageAnalyzer |
| NC4003 | Type | Error | DecimalUsageAnalyzer |
Expand All @@ -26,3 +26,4 @@
| NC4024 | Usage | Error | MultipleCatchBlockAnalyzer |
| NC4025 | Method | Error | EnumMethodsUsageAnalyzer |
| NC4026 | Usage | Error | SystemDiagnosticsUsageAnalyzer |
| NC4027 | Usage | Error | BigIntegerUninitializedAnalyzer |
147 changes: 147 additions & 0 deletions src/Neo.SmartContract.Analyzer/BigIntegerUninitializedAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;

namespace Neo.SmartContract.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class BigIntegerUninitializedAnalyzer : DiagnosticAnalyzer
{
public const string DiagnosticId = "NC4027";

private static readonly DiagnosticDescriptor Rule = new(
DiagnosticId,
"Uninitialized BigInteger",
"BigInteger must be initialized when declared",
"Usage",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(Rule);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, SyntaxKind.VariableDeclaration);
}

private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context)
{
var variableDeclaration = (VariableDeclarationSyntax)context.Node;

// Check if the type is BigInteger
if (context.SemanticModel.GetTypeInfo(variableDeclaration.Type).Type?.ToString() != "System.Numerics.BigInteger")
return;

// Check if the declaration is inside a struct
bool isInStruct = variableDeclaration.Ancestors().OfType<StructDeclarationSyntax>().Any();

foreach (var variable in variableDeclaration.Variables)
{
if (variable.Initializer == null)
{
if (!isInStruct)
{
// Report diagnostic for non-struct BigInteger declarations without initialization
var diagnostic = Diagnostic.Create(Rule, variable.GetLocation());
context.ReportDiagnostic(diagnostic);
}
else
{
// For structs, check if there's a constructor that initializes this field
var structDeclaration = variableDeclaration.Ancestors().OfType<StructDeclarationSyntax>().First();
var constructors = structDeclaration.Members.OfType<ConstructorDeclarationSyntax>();

bool isInitializedInConstructor = false;

Check warning on line 65 in src/Neo.SmartContract.Analyzer/BigIntegerUninitializedAnalyzer.cs

View workflow job for this annotation

GitHub Actions / Test

The variable 'isInitializedInConstructor' is assigned but its value is never used
foreach (var constructor in constructors)
{
// Check if the field is initialized in any constructor
var assignments = constructor.Body?.DescendantNodes()
.OfType<AssignmentExpressionSyntax>()
.Where(assignment =>
{
if (assignment.Left is MemberAccessExpressionSyntax memberAccess)
{
return memberAccess.Name.Identifier.ValueText == variable.Identifier.ValueText;
}
return false;
});

if (assignments != null && assignments.Any())
{
isInitializedInConstructor = true;
break;
}
}

// if (!isInitializedInConstructor && !constructors.Any())
// {
// // Report diagnostic if the BigInteger field is not initialized in any constructor
// // and there are no explicit constructors
// var diagnostic = Diagnostic.Create(Rule, variable.GetLocation());
// context.ReportDiagnostic(diagnostic);
// }
}
}
}
}
}

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(BigIntegerUninitializedCodeFixProvider)), Shared]
public class BigIntegerUninitializedCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds
=> ImmutableArray.Create(BigIntegerUninitializedAnalyzer.DiagnosticId);

public sealed override FixAllProvider GetFixAllProvider()
=> WellKnownFixAllProviders.BatchFixer;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

var declaration = root?.FindToken(diagnosticSpan.Start)
.Parent?.AncestorsAndSelf()
.OfType<VariableDeclaratorSyntax>()
.First();

if (declaration is null) return;

context.RegisterCodeFix(
CodeAction.Create(
title: "Initialize with zero",
createChangedDocument: c => InitializeWithZero(context.Document, declaration, c),
equivalenceKey: "Initialize with zero"),
diagnostic);
}

private static async Task<Document> InitializeWithZero(Document document,
VariableDeclaratorSyntax declarator,
CancellationToken cancellationToken)
{
var initializer = SyntaxFactory.EqualsValueClause(
SyntaxFactory.LiteralExpression(
SyntaxKind.NumericLiteralExpression,
SyntaxFactory.Literal(0)));

var newDeclarator = declarator.WithInitializer(initializer);

var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = root?.ReplaceNode(declarator, newDeclarator);

return document.WithSyntaxRoot(newRoot!);
}
}
}
2 changes: 1 addition & 1 deletion src/Neo.SmartContract.Analyzer/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ This repository contains a set of Roslyn analyzers and code fix providers for Ne
- [StaticFieldInitializationAnalyzer.cs](NeoContractAnalyzer/StaticFieldInitializationAnalyzer.cs): This analyzer checks for proper initialization of static fields in smart contracts.
- [MultipleCatchBlockAnalyzer.cs](NeoContractAnalyzer/MultipleCatchBlockAnalyzer.cs): This analyzer checks for multiple catch blocks in try statements.
- [SystemDiagnosticsUsageAnalyzer.cs](NeoContractAnalyzer/SystemDiagnosticsUsageAnalyzer.cs): This analyzer detects and reports usage of System.Diagnostics namespace, which is not supported in Neo smart contracts.

- [BigIntegerUninitializedAnalyzer.cs](NeoContractAnalyzer/BigIntegerUninitializedAnalyzer.cs): This analyzer checks for uninitialized BigInteger declarations.
## How to Use

To use these analyzers in your Neo smart contract project:
Expand Down
10 changes: 10 additions & 0 deletions tests/Neo.Compiler.CSharp.UnitTests/UnitTest_ClassInit.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,17 @@ public struct IntInit
public BigInteger B;
}

public class IntInitClass
{
public int A;
public BigInteger B;
}

[TestMethod]
public void Test_InitInt()
{
var cs = new IntInit();
var csClass = new IntInitClass();

using var fee = Engine.CreateGasWatcher();
var result = Contract.TestInitInt();
Expand All @@ -25,6 +32,9 @@ public void Test_InitInt()

Assert.AreEqual(cs.A, (BigInteger)result[0]);
Assert.AreEqual(cs.B, (BigInteger)result[1]);
Assert.AreEqual(cs.B, 0);
Assert.AreEqual(csClass.B, 0);

}

[TestMethod]
Expand Down
Loading