Работа вокруг для 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/