C# CodeDom 的解决方法导致 csc.exe 中堆栈溢出 (CS1647)?

发布于 2024-07-23 02:50:30 字数 2098 浏览 9 评论 0原文

我遇到了一种情况,我需要生成一个具有大字符串常量的类。 超出我控制范围的代码会导致我生成的 CodeDom 树被发送到 C# 源代码,然后编译为更大的程序集的一部分。

不幸的是,我遇到了一种情况,如果该字符串的长度在 Win2K8 x64 中超过 335440 个字符(在 Win2K3 x86 中为 926240),C# 编译器将退出并出现致命错误:

致命错误 CS1647:表达式太长或太复杂,无法在 'int 附近编译'

MSDN 说 CS1647 是“编译器中的堆栈溢出”(没有双关语!)。 更仔细地观察,我发现 CodeDom“很好地”将我的字符串 const 包装为 80 个字符。这导致编译器连接超过 4193 个字符串块,这显然是 x64 NetFx 中 C# 编译器的堆栈深度。 CSC.exe 必须在内部递归地计算此表达式以“补充”我的单个字符串。

我最初的问题是:“有人知道改变代码生成器发出字符串方式的解决方法吗?”我无法控制外部系统使用 C# 源作为中间体的事实,我想要这是一个常量(而不是运行时的字符串连接)。

或者,如何制定此表达式,以便在一定数量的字符之后,我仍然能够创建一个常量,但它由多个块组成?

完整重现在这儿:

// this string breaks CSC: 335440 is Win2K8 x64 max, 926240 is Win2K3 x86 max
string HugeString = new String('X', 926300);

CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
CodeCompileUnit code = new CodeCompileUnit();

// namespace Foo {}
CodeNamespace ns = new CodeNamespace("Foo");
code.Namespaces.Add(ns);

// public class Bar {}
CodeTypeDeclaration type = new CodeTypeDeclaration();
type.IsClass = true;
type.Name = "Bar";
type.Attributes = MemberAttributes.Public;
ns.Types.Add(type);

// public const string HugeString = "XXXX...";

CodeMemberField field = new CodeMemberField();
field.Name = "HugeString";
field.Type = new CodeTypeReference(typeof(String));
field.Attributes = MemberAttributes.Public|MemberAttributes.Const;
field.InitExpression = new CodePrimitiveExpression(HugeString);
type.Members.Add(field);

// generate class file
using (TextWriter writer = File.CreateText("FooBar.cs"))
{
    provider.GenerateCodeFromCompileUnit(code, writer, new CodeGeneratorOptions());
}

// compile class file
CompilerResults results = provider.CompileAssemblyFromFile(new CompilerParameters(), "FooBar.cs");

// output reults
foreach (string msg in results.Output)
{
    Console.WriteLine(msg);
}

// output errors
foreach (CompilerError error in results.Errors)
{
    Console.WriteLine(error);
}

I've got a situation where I need to generate a class with a large string const. Code outside of my control causes my generated CodeDom tree to be emitted to C# source and then later compiled as part of a larger Assembly.

Unfortunately, I've run into a situation whereby if the length of this string exceeds 335440 chars in Win2K8 x64 (926240 in Win2K3 x86), the C# compiler exits with a fatal error:

fatal error CS1647: An expression is too long or complex to compile near 'int'

MSDN says CS1647 is "a stack overflow in the compiler" (no pun intended!). Looking more closely I've determined that the CodeDom "nicely" wraps my string const at 80 chars.This causes the compiler to concatenate over 4193 string chunks which apparently is the stack depth of the C# compiler in x64 NetFx. CSC.exe must internally recursively evaluate this expression to "rehydrate" my single string.

My initial question is this: "does anyone know of a work-around to change how the code generator emits strings?" I cannot control the fact that the external system uses C# source as an intermediate and I want this to be a constant (rather than a runtime concatenation of strings).

Alternatively, how can I formulate this expression such that after a certain number of chars, I am still able to create a constant but it is composed of multiple large chunks?

Full repro is here:

// this string breaks CSC: 335440 is Win2K8 x64 max, 926240 is Win2K3 x86 max
string HugeString = new String('X', 926300);

CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
CodeCompileUnit code = new CodeCompileUnit();

// namespace Foo {}
CodeNamespace ns = new CodeNamespace("Foo");
code.Namespaces.Add(ns);

// public class Bar {}
CodeTypeDeclaration type = new CodeTypeDeclaration();
type.IsClass = true;
type.Name = "Bar";
type.Attributes = MemberAttributes.Public;
ns.Types.Add(type);

// public const string HugeString = "XXXX...";

CodeMemberField field = new CodeMemberField();
field.Name = "HugeString";
field.Type = new CodeTypeReference(typeof(String));
field.Attributes = MemberAttributes.Public|MemberAttributes.Const;
field.InitExpression = new CodePrimitiveExpression(HugeString);
type.Members.Add(field);

// generate class file
using (TextWriter writer = File.CreateText("FooBar.cs"))
{
    provider.GenerateCodeFromCompileUnit(code, writer, new CodeGeneratorOptions());
}

// compile class file
CompilerResults results = provider.CompileAssemblyFromFile(new CompilerParameters(), "FooBar.cs");

// output reults
foreach (string msg in results.Output)
{
    Console.WriteLine(msg);
}

// output errors
foreach (CompilerError error in results.Errors)
{
    Console.WriteLine(error);
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

爱,才寂寞 2024-07-30 02:50:30

使用 CodeSnippetExpression 和手动引用的字符串,我能够发出我希望从 Microsoft.CSharp.CSharpCodeGenerator 看到的源代码。

因此,要回答上述问题,请将这一行: 替换

field.InitExpression = new CodePrimitiveExpression(HugeString);

为:

field.InitExpression = new CodeSnippetExpression(QuoteSnippetStringCStyle(HugeString));

最后修改引用 Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyle 方法的私有字符串,使其在 80 个字符后不换行:

private static string QuoteSnippetStringCStyle(string value)
{
    // CS1647: An expression is too long or complex to compile near '...'
    // happens if number of line wraps is too many (335440 is max for x64, 926240 is max for x86)

    // CS1034: Compiler limit exceeded: Line cannot exceed 16777214 characters
    // theoretically every character could be escaped unicode (6 chars), plus quotes, etc.

    const int LineWrapWidth = (16777214/6) - 4;
    StringBuilder b = new StringBuilder(value.Length+5);

    b.Append("\r\n\"");
    for (int i=0; i<value.Length; i++)
    {
        switch (value[i])
        {
            case '\u2028':
            case '\u2029':
            {
                int ch = (int)value[i];
                b.Append(@"\u");
                b.Append(ch.ToString("X4", CultureInfo.InvariantCulture));
                break;
            }
            case '\\':
            {
                b.Append(@"\\");
                break;
            }
            case '\'':
            {
                b.Append(@"\'");
                break;
            }
            case '\t':
            {
                b.Append(@"\t");
                break;
            }
            case '\n':
            {
                b.Append(@"\n");
                break;
            }
            case '\r':
            {
                b.Append(@"\r");
                break;
            }
            case '"':
            {
                b.Append("\\\"");
                break;
            }
            case '\0':
            {
                b.Append(@"\0");
                break;
            }
            default:
            {
                b.Append(value[i]);
                break;
            }
        }

        if ((i > 0) && ((i % LineWrapWidth) == 0))
        {
            if ((Char.IsHighSurrogate(value[i]) && (i < (value.Length - 1))) && Char.IsLowSurrogate(value[i + 1]))
            {
                b.Append(value[++i]);
            }
            b.Append("\"+\r\n");
            b.Append('"');
        }
    }
    b.Append("\"");
    return b.ToString();
}

Using a CodeSnippetExpression and a manually quoted string, I was able to emit the source that I would have liked to have seen from Microsoft.CSharp.CSharpCodeGenerator.

So to answer the question above, replace this line:

field.InitExpression = new CodePrimitiveExpression(HugeString);

with this:

field.InitExpression = new CodeSnippetExpression(QuoteSnippetStringCStyle(HugeString));

And finally modify the private string quoting Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyle method to not wrap after 80 chars:

private static string QuoteSnippetStringCStyle(string value)
{
    // CS1647: An expression is too long or complex to compile near '...'
    // happens if number of line wraps is too many (335440 is max for x64, 926240 is max for x86)

    // CS1034: Compiler limit exceeded: Line cannot exceed 16777214 characters
    // theoretically every character could be escaped unicode (6 chars), plus quotes, etc.

    const int LineWrapWidth = (16777214/6) - 4;
    StringBuilder b = new StringBuilder(value.Length+5);

    b.Append("\r\n\"");
    for (int i=0; i<value.Length; i++)
    {
        switch (value[i])
        {
            case '\u2028':
            case '\u2029':
            {
                int ch = (int)value[i];
                b.Append(@"\u");
                b.Append(ch.ToString("X4", CultureInfo.InvariantCulture));
                break;
            }
            case '\\':
            {
                b.Append(@"\\");
                break;
            }
            case '\'':
            {
                b.Append(@"\'");
                break;
            }
            case '\t':
            {
                b.Append(@"\t");
                break;
            }
            case '\n':
            {
                b.Append(@"\n");
                break;
            }
            case '\r':
            {
                b.Append(@"\r");
                break;
            }
            case '"':
            {
                b.Append("\\\"");
                break;
            }
            case '\0':
            {
                b.Append(@"\0");
                break;
            }
            default:
            {
                b.Append(value[i]);
                break;
            }
        }

        if ((i > 0) && ((i % LineWrapWidth) == 0))
        {
            if ((Char.IsHighSurrogate(value[i]) && (i < (value.Length - 1))) && Char.IsLowSurrogate(value[i + 1]))
            {
                b.Append(value[++i]);
            }
            b.Append("\"+\r\n");
            b.Append('"');
        }
    }
    b.Append("\"");
    return b.ToString();
}
甜警司 2024-07-30 02:50:30

那么我说的对吗?您已经获得了 C# 源文件,其中包含以下内容:

public const HugeString = "xxxxxxxxxxxx...." +
    "yyyyy....." +
    "zzzzz.....";

然后您然后尝试编译它?

如果是这样,我会尝试在编译之前编辑文本文件(当然是代码)。 这应该相对简单,因为它们可能会遵循严格定义的模式(与人类生成的源代码相比)。 将其转换为每个常数都有一条粗线。 如果您想要一些示例代码来尝试此操作,请告诉我。

顺便说一句,您的重现成功,在我的盒子上没有错误 - 您使用的是哪个版本的框架? (我的盒子打开了 4.0 的测试版,这可能会影响一些事情。)

编辑:将其更改为不是字符串常量怎么样? 您需要自己将其分解,并将其作为公共静态只读字段发出,如下所示:

public static readonly HugeString = "xxxxxxxxxxxxxxxx" + string.Empty +
    "yyyyyyyyyyyyyyyyyyy" + string.Empty +
    "zzzzzzzzzzzzzzzzzzz";

至关重要的是,string.Empty是一个public static readonly字段,不是一个常数。 这意味着 C# 编译器只会发出对 string.Concat 的调用,这可能没问题。 当然,它只会在执行时发生一次 - 比编译时慢,但它可能是比其他任何方法更简单的解决方法。

So am I right in saying you've got the C# source file with something like:

public const HugeString = "xxxxxxxxxxxx...." +
    "yyyyy....." +
    "zzzzz.....";

and you then try to compile it?

If so, I would try to edit the text file (in code, of course) before compiling. That should be relatively straightforward to do, as presumably they'll follow a rigidly-defined pattern (compared with human-generated source code). Convert it to have a single massive line for each constant. Let me know if you'd like some sample code to try this.

By the way, your repro succeeds with no errors on my box - which version of the framework are you using? (My box has the beta of 4.0 on, which may affect things.)

EDIT: How about changing it to not be a string constant? You'd need to break it up yourself, and emit it as a public static readonly field like this:

public static readonly HugeString = "xxxxxxxxxxxxxxxx" + string.Empty +
    "yyyyyyyyyyyyyyyyyyy" + string.Empty +
    "zzzzzzzzzzzzzzzzzzz";

Crucially, string.Empty is a public static readonly field, not a constant. That means the C# compiler will just emit a call to string.Concat which may well be okay. It'll only happen once at execution time of course - slower than doing it at compile-time, but it may be an easier workaround than anything else.

皇甫轩 2024-07-30 02:50:30

请注意,如果将字符串声明为 const,则在其代码中使用该字符串的每个程序集中都会复制该字符串。

使用静态只读可能会更好。

另一种方法是声明一个返回字符串的只读属性。

Note that if you declare the string as const, it will be copied in each assembly that uses this string in its code.

You may be better off with static readonly.

Another way would be to declare a readonly property that returns the string.

执笏见 2024-07-30 02:50:30

我不知道如何更改代码生成器的行为,但您可以通过 /stack EditBin.EXE

示例:

editbin /stack:100000,1000 csc.exe <options>

以下是其使用示例:

class App 
{
    private static long _Depth = 0;

    // recursive function to blow stack
    private static void GoDeep() 
    {
        if ((++_Depth % 10000) == 0) System.Console.WriteLine("Depth is " +
            _Depth.ToString());
        GoDeep();
    return;
    }

    public static void Main() {
        try 
        {
            GoDeep();
        } 
        finally 
        {
        }

        return;
    }
}




editbin /stack:100000,1000 q.exe
Depth is 10000
Depth is 20000

Unhandled Exception: StackOverflowException.

editbin /stack:1000000,1000 q.exe
Depth is 10000
Depth is 20000
Depth is 30000
Depth is 40000
Depth is 50000
Depth is 60000
Depth is 70000
Depth is 80000

Unhandled Exception: StackOverflowException.

I have no idea how to change the behavior of the code generator, but you can change the stack size that the compiler uses with the /stack option of EditBin.EXE.

Example:

editbin /stack:100000,1000 csc.exe <options>

Following is an example of its use:

class App 
{
    private static long _Depth = 0;

    // recursive function to blow stack
    private static void GoDeep() 
    {
        if ((++_Depth % 10000) == 0) System.Console.WriteLine("Depth is " +
            _Depth.ToString());
        GoDeep();
    return;
    }

    public static void Main() {
        try 
        {
            GoDeep();
        } 
        finally 
        {
        }

        return;
    }
}




editbin /stack:100000,1000 q.exe
Depth is 10000
Depth is 20000

Unhandled Exception: StackOverflowException.

editbin /stack:1000000,1000 q.exe
Depth is 10000
Depth is 20000
Depth is 30000
Depth is 40000
Depth is 50000
Depth is 60000
Depth is 70000
Depth is 80000

Unhandled Exception: StackOverflowException.
A君 2024-07-30 02:50:30

确保 IIS 中的应用程序池启用了 32 位应用程序。 这就是我尝试在 Win7 64 位中编译 32 位应用程序时解决此问题所需的全部内容。 奇怪的是(或者说不是),微软无法提供这个答案。 经过一整天的搜索,我在 Iron Speed Designer 论坛上找到了修复的链接:

http://darrell.mozingo.net/2009/01/17/running-iis-7-in-32-bit-mode/

Make sure the application pools in IIS have 32-bit applications enabled. That's all it took for me to cure this problem trying to compile a 32-bit app in Win7 64-bit. Oddly (or not), Microsoft could not supply this answer. After a full day of searching, I found this link to the fix on an Iron Speed Designer forum:

http://darrell.mozingo.net/2009/01/17/running-iis-7-in-32-bit-mode/

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文