diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs index 7270c301..89544d5d 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.Predicates.cs @@ -1454,10 +1454,15 @@ private static bool IsUnchecked(string typeName, long signedValue, bool isNegati case "uint": case "UInt32": + { + return false; + } + case "nuint": case "UIntPtr": { - return false; + var unsignedValue = unchecked((ulong)signedValue); + return unsignedValue is < uint.MinValue or > uint.MaxValue; } case "ulong": diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs index bd29843b..77382cdd 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.VisitDecl.cs @@ -2587,7 +2587,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) + if (typeNameBacking is "nuint" or "UIntPtr") { goto case CXType_UInt; } @@ -2620,7 +2620,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeBackingSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) + if (typeNameBacking is "nint" or "IntPtr") { goto case CXType_Int; } @@ -2686,7 +2686,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record case CXType_ULongLong: { - if (typeNameBacking.Equals("nuint", StringComparison.Ordinal)) + if (typeNameBacking is "nuint" or "UIntPtr") { goto case CXType_UInt; } @@ -2719,7 +2719,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record { isTypeSigned = true; - if (typeNameBacking.Equals("nint", StringComparison.Ordinal)) + if (typeNameBacking is "nint" or "IntPtr") { goto case CXType_Int; } @@ -2773,7 +2773,7 @@ void VisitBitfieldDecl(FieldDecl fieldDecl, BitfieldDesc[] bitfieldDescs, Record // Signed types are sign extended when shifted var isUnsignedToSigned = !isTypeBackingSigned && isTypeSigned; - + // Check if type is directly shiftable/maskable // Remapped types are not guaranteed to be shiftable or maskable // Enums are maskable, but not shiftable @@ -3761,6 +3761,14 @@ void ForDeclStmt(VarDecl varDecl, DeclStmt declStmt) private bool IsConstant(string targetTypeName, Expr initExpr) { + // Constant expressions for native integers must be in range of the corresponding 32-bit integer type + // Also see: https://github.com/dotnet/csharplang/blob/main/proposals/csharp-9.0/native-integers.md + if ((targetTypeName is "nuint" or "UIntPtr" && initExpr.Handle.Evaluate is { Kind: CXEval_Int, AsUnsigned: > uint.MaxValue }) + || (targetTypeName is "nint" or "IntPtr" && initExpr.Handle.Evaluate is { Kind: CXEval_Int, AsLongLong: < int.MinValue or > int.MaxValue })) + { + return false; + } + if (IsTypePointerOrReference(initExpr) && !targetTypeName.Equals("string", StringComparison.Ordinal)) { return false; diff --git a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs index 87bc0e58..7a4b4f99 100644 --- a/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs +++ b/sources/ClangSharp.PInvokeGenerator/PInvokeGenerator.cs @@ -3359,6 +3359,14 @@ private void UncheckStmt(string targetTypeName, Stmt stmt) } } + // Cast to output type if out of range of the corresponding 32-bit integer type + if (IsPrevContextDecl(out _, out _) && !IsStmtAsWritten(stmt, out _, removeParens: true) + && ((targetTypeName is "nuint" or "UIntPtr" && stmt.Handle.Evaluate.AsUnsigned > uint.MaxValue) + || (targetTypeName is "nint" or "IntPtr" && stmt.Handle.Evaluate.AsLongLong is < int.MinValue or > int.MaxValue))) + { + needsCast = true; + } + if (needsCast) { _outputBuilder.BeginInnerValue(); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs index a068c33f..5bfb7be2 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpCompatibleUnix/VarDeclarationTest.cs @@ -267,7 +267,7 @@ namespace ClangSharp.Test public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const IntPtr MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly IntPtr MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs index 3ec691d8..4b06c22c 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpDefaultUnix/VarDeclarationTest.cs @@ -268,7 +268,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs index c72f00c1..09a0fc74 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpLatestUnix/VarDeclarationTest.cs @@ -268,7 +268,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs index 0ab01dfb..1bad9672 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CSharpPreviewUnix/VarDeclarationTest.cs @@ -270,7 +270,7 @@ protected override Task UncheckedConversionMacroTestImpl() public static partial class Methods {{ [NativeTypeName(""#define MyMacro1 (long)0x80000000L"")] - public const nint MyMacro1 = unchecked((nint)(0x80000000)); + public static readonly nint MyMacro1 = unchecked((nint)(0x80000000)); [NativeTypeName(""#define MyMacro2 (int)0x80000000"")] public const int MyMacro2 = unchecked((int)(0x80000000)); diff --git a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs index 22d264a7..e60f957b 100644 --- a/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs +++ b/tests/ClangSharp.PInvokeGenerator.UnitTests/CTest.cs @@ -1002,4 +1002,216 @@ readonly get { "Flags", "Flags" } }); } + + [Test] + [Platform("unix")] // This test has platform-specific differences (on Windows, __LONG_MAX__ is 32-bit) + public Task CLongDefinesTestUnix() + { + // C longs differ based on platform + // These values are taken from the Linux headers when using Clang + var inputContents = @" +// stdint.h +#define SIZE_MAX (18446744073709551615UL) + +// cl_ext.h from OpenCL +#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX + +// limits.h +#define LONG_MAX __LONG_MAX__ +#define ULONG_MAX (__LONG_MAX__ *2UL+1UL) + +// These expressions never exceed the allowed range, so should remain const +#define CONST_IN_RANGE_S1 ((long)2147483647) +#define CONST_IN_RANGE_U1 ((unsigned long)4294967295) + +// These expressions exceed the allowed range, so should become static readonly +#define READONLY_OUT_OF_RANGE_S1 ((long)4294967295) +#define READONLY_OUT_OF_RANGE_S2 ((long)4294967296) +#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296) +"; + + // We use "static readonly" instead of "const" because nint/nuint differ on 32/64-bit platforms + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define SIZE_MAX (18446744073709551615UL)"")] + public static readonly nuint SIZE_MAX = unchecked((nuint)(18446744073709551615U)); + + [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] + public static readonly nuint CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = unchecked((nuint)(18446744073709551615U)); + + [NativeTypeName(""#define LONG_MAX __LONG_MAX__"")] + public static readonly nint LONG_MAX = unchecked((nint)(9223372036854775807)); + + [NativeTypeName(""#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)"")] + public static readonly nuint ULONG_MAX = unchecked((nuint)(9223372036854775807 * 2U + 1U)); + + [NativeTypeName(""#define CONST_IN_RANGE_S1 ((long)2147483647)"")] + public const nint CONST_IN_RANGE_S1 = ((nint)(2147483647)); + + [NativeTypeName(""#define CONST_IN_RANGE_U1 ((unsigned long)4294967295)"")] + public const nuint CONST_IN_RANGE_U1 = ((nuint)(4294967295)); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_S1 ((long)4294967295)"")] + public static readonly nint READONLY_OUT_OF_RANGE_S1 = unchecked((nint)(4294967295)); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_S2 ((long)4294967296)"")] + public static readonly nint READONLY_OUT_OF_RANGE_S2 = unchecked((nint)(4294967296)); + + [NativeTypeName(""#define READONLY_OUT_OF_RANGE_U1 ((unsigned long)4294967296)"")] + public static readonly nuint READONLY_OUT_OF_RANGE_U1 = unchecked((nuint)(4294967296)); + } +} +"; + + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + + [Test] + [Platform("unix")] + public Task CLongDefinesRegressionTestUnix() + { + // This test is to catch a potential regression when changing how native integers are handled + // Specifically, (18446744073709551615U) became unchecked(18446744073709551615U) + + // Macro values are taken from the Linux headers when using Clang + // Some values are substituted for simplicity + var inputContents = @" +// __stddef_size_t.h +typedef __SIZE_TYPE__ size_t; + +// stdbool.h +#define bool _Bool +#define true 1 +#define false 0 + +// SDL_stdinc.h from SDL3 +bool SDL_size_add_check_overflow(size_t a, size_t b, size_t *ret) +{ + if (b > (18446744073709551615UL) - a) { + return false; + } + *ret = a + b; + return true; +} +"; + + // The expected below currently does not represent the ideal behavior. + // Ideally, the unchecked keywork is removed as it is unnecessary. + // This issue is tracked here: https://github.com/dotnet/ClangSharp/issues/709 + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static unsafe partial class Methods + { + [return: NativeTypeName(""_Bool"")] + public static bool SDL_size_add_check_overflow([NativeTypeName(""size_t"")] nuint a, [NativeTypeName(""size_t"")] nuint b, [NativeTypeName(""size_t *"")] nuint* ret) + { + if (b > unchecked(18446744073709551615U) - a) + { + return (0) != 0; + } + + *ret = a + b; + return (1) != 0; + } + + [NativeTypeName(""#define true 1"")] + public const int @true = 1; + + [NativeTypeName(""#define false 0"")] + public const int @false = 0; + } +} +"; + + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + + [Test] + [Platform("win")] // This test has platform-specific differences + public Task CLongDefinesTestWindows() + { + // C longs differ based on platform + // These values are taken from the Windows headers when using MSVC + var inputContents = @" +// limits.h +#define SIZE_MAX 0xffffffffffffffffui64 + +// cl_ext.h from OpenCL +#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX + +// limits.h +#define LONG_MAX 2147483647L +#define ULONG_MAX 0xffffffffUL +"; + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define SIZE_MAX 0xffffffffffffffffui64"")] + public const ulong SIZE_MAX = 0xffffffffffffffffUL; + + [NativeTypeName(""#define CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM SIZE_MAX"")] + public const ulong CL_IMPORT_MEMORY_WHOLE_ALLOCATION_ARM = 0xffffffffffffffffUL; + + [NativeTypeName(""#define LONG_MAX 2147483647L"")] + public const int LONG_MAX = 2147483647; + + [NativeTypeName(""#define ULONG_MAX 0xffffffffUL"")] + public const uint ULONG_MAX = 0xffffffffU; + } +} +"; + + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + + [Test] + public Task IntptrDefineTest() + { + string inputContents; + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // These values are taken from the Windows headers when using MSVC + inputContents = @" +// vcruntime.h +typedef __int64 intptr_t; + +// cl_ext.h from OpenCL +#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331) +"; + } + else + { + // These values are taken from the Linux headers when using Clang + inputContents = @" +// stdint.h +typedef long int intptr_t; + +// cl_ext.h from OpenCL +#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331) +"; + } + + var expectedOutputContents = @"namespace ClangSharp.Test +{ + public static partial class Methods + { + [NativeTypeName(""#define CL_ICD2_TAG_KHR ((intptr_t)0x4F50454E434C3331)"")] + public static readonly nint CL_ICD2_TAG_KHR = unchecked((nint)(0x4F50454E434C3331)); + } +} +"; + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return ValidateGeneratedCSharpLatestWindowsBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + else + { + return ValidateGeneratedCSharpLatestUnixBindingsAsync(inputContents, expectedOutputContents, commandLineArgs: DefaultCClangCommandLineArgs, language: "c", languageStandard: DefaultCStandard); + } + } }