Работа вокруг для C# CodeDom, вызывающего переполнение стека (CS1647) в csc.exe?

У меня есть ситуация, когда мне нужно создать класс с большой строкой const. Код вне моего контроля заставляет мое сгенерированное дерево CodeDom излучаться в источник C#, а затем компилироваться как часть большей сборки.

к сожалению, я столкнулся с ситуацией, когда, если длина этой строки превышает 335440 символов в Win2K8 x64 (926240 в Win2K3 x86), компилятор C# выходит с фатальной ошибкой:

фатальная ошибка CS1647: An выражение слишком длинное или сложное для компиляции рядом с 'int'

MSDN говорит, что CS1647 - это "переполнение стека в компиляторе" (Не каламбур!). Присмотревшись внимательнее, я определил, что CodeDom "красиво" обертывает мою строку const на 80 символов.Это заставляет компилятор объединять более 4193 строковых фрагментов, которые, по-видимому, являются глубиной стека компилятора C# в x64 NetFx. КДВ.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);
}

5 ответов


используя 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 '':
            {
                b.Append(@"");
                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();
}

так я прав, говоря, что у вас есть исходный файл C# с чем-то вроде:

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

и затем попробуйте скомпилировать его?

если это так, я попытаюсь отредактировать текстовый файл (в коде, конечно) перед компиляцией. Это должно быть относительно просто сделать, поскольку, предположительно, они будут следовать жестко определенному шаблону (по сравнению с исходным кодом, созданным человеком). Преобразуйте его в одну массивную линию для каждой константы. Дай мне знать, если захочешь. пример кода, чтобы попробовать это.

Кстати, Ваш репро преуспевает без ошибок на моей коробке-какую версию фреймворка вы используете? (В моей коробке есть бета-версия 4.0, которая может повлиять на вещи.)

EDIT: как насчет изменения его, чтобы не быть Строковой константой? Вам нужно будет разбить его самостоятельно и излучать его как публичное статическое поле только для чтения, как это:

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

самое главное, string.Empty это public static readonly поле, не константы. Это означает компилятор C# будет просто излучать вызов string.Concat что вполне может быть хорошо. Конечно, это произойдет только один раз во время выполнения - медленнее, чем во время компиляции, но это может быть проще, чем что-либо еще.


обратите внимание, что если вы объявите строку как const, то это будет скопировал в каждой сборке, которая использует эту строку в свой код.

вам может быть лучше со статическим readonly.

другой способ-объявить свойство readonly, которое возвращает строку.


Я понятия не имею, как изменить поведение генератора кода, но вы можете изменить размер стека, что компилятор использует с / 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.

убедитесь, что в пулах приложений IIS включены 32-разрядные приложения. Это все, что мне потребовалось, чтобы вылечить эту проблему, пытаясь скомпилировать 32-разрядное приложение в 64-разрядном Win7. Как ни странно (или нет), Microsoft не смогла предоставить этот ответ. После целого дня поиска я нашел эту ссылку на исправление на форуме Iron Speed Designer:

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