• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

xoofx / CppAst.NET / 14782910566

01 May 2025 08:25PM UTC coverage: 78.46% (-0.1%) from 78.596%
14782910566

push

github

xoofx
Add CppInclusionDirective

1147 of 1868 branches covered (61.4%)

Branch coverage included in aggregate %.

11 of 12 new or added lines in 4 files covered. (91.67%)

238 existing lines in 4 files now uncovered.

4965 of 5922 relevant lines covered (83.84%)

2224.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.82
/src/CppAst/CppParser.cs
1
// Copyright (c) Alexandre Mutel. All rights reserved.
2
// Licensed under the BSD-Clause 2 license.
3
// See license.txt file in the project root for full license information.
4

5
using System;
6
using System.Collections.Generic;
7
using System.IO;
8
using System.Linq;
9
using System.Runtime.InteropServices;
10
using System.Text;
11
using ClangSharp.Interop;
12

13
namespace CppAst
14
{
15
    /// <summary>
16
    /// C/C++ Parser entry point functions.
17
    /// </summary>
18
    public static class CppParser
19
    {
20
        public const string CppAstRootFileName = "cppast.input";
21

22
        /// <summary>
23
        /// Parse the specified C++ text in-memory.
24
        /// </summary>
25
        /// <param name="cppText">A string with a C/C++ text</param>
26
        /// <param name="options">Options used for parsing this file (e.g include folders...)</param>
27
        /// <param name="cppFilename">Optional path to a file only used for reporting errors. Default is 'content'</param>
28
        /// <returns>The result of the compilation</returns>
29
        public static CppCompilation Parse(string cppText, CppParserOptions options = null, string cppFilename = "content")
30
        {
31
            if (cppText == null) throw new ArgumentNullException(nameof(cppText));
94!
32
            var cppFiles = new List<CppFileOrString> { new CppFileOrString() { Filename = cppFilename, Content = cppText, } };
94✔
33
            return ParseInternal(cppFiles, options);
94✔
34
        }
35

36
        /// <summary>
37
        /// Parse the specified single file.
38
        /// </summary>
39
        /// <param name="cppFilename">A path to a C/C++ file on the disk to parse</param>
40
        /// <param name="options">Options used for parsing this file (e.g include folders...)</param>
41
        /// <returns>The result of the compilation</returns>
42
        public static CppCompilation ParseFile(string cppFilename, CppParserOptions options = null)
43
        {
44
            if (cppFilename == null) throw new ArgumentNullException(nameof(cppFilename));
93!
45
            var files = new List<string>() { cppFilename };
93✔
46
            return ParseFiles(files, options);
93✔
47
        }
48

49
        /// <summary>
50
        /// Parse the specified single file.
51
        /// </summary>
52
        /// <param name="cppFilenameList">A list of path to C/C++ header files on the disk to parse</param>
53
        /// <param name="options">Options used for parsing this file (e.g include folders...)</param>
54
        /// <returns>The result of the compilation</returns>
55
        public static CppCompilation ParseFiles(List<string> cppFilenameList, CppParserOptions options = null)
56
        {
57
            if (cppFilenameList == null) throw new ArgumentNullException(nameof(cppFilenameList));
93!
58

59
            var cppFiles = new List<CppFileOrString>();
93✔
60
            foreach (var cppFilepath in cppFilenameList)
372✔
61
            {
62
                if (string.IsNullOrEmpty(cppFilepath)) throw new InvalidOperationException("A null or empty filename is invalid in the list");
93!
63
                cppFiles.Add(new CppFileOrString() { Filename = cppFilepath });
93✔
64
            }
65
            return ParseInternal(cppFiles, options);
93✔
66
        }
67

68
        /// <summary>
69
        /// Private method parsing file or content.
70
        /// </summary>
71
        /// <param name="cppFiles">A list of path to C/C++ header files on the disk to parse</param>
72
        /// <param name="options">Options used for parsing this file (e.g include folders...)</param>
73
        /// <returns>The result of the compilation</returns>
74
        private static unsafe CppCompilation ParseInternal(List<CppFileOrString> cppFiles, CppParserOptions options = null)
75
        {
76
            if (cppFiles == null) throw new ArgumentNullException(nameof(cppFiles));
187!
77

78
            options = options ?? new CppParserOptions();
187✔
79

80
            var arguments = new List<string>();
187✔
81

82
            // Make sure that paths are absolute
83
            var normalizedIncludePaths = new List<string>();
187✔
84
            normalizedIncludePaths.AddRange(options.IncludeFolders.Select(x => Path.Combine(Environment.CurrentDirectory, x)));
187✔
85

86
            var normalizedSystemIncludePaths = new List<string>();
187✔
87
            normalizedSystemIncludePaths.AddRange(options.SystemIncludeFolders.Select(x => Path.Combine(Environment.CurrentDirectory, x)));
187✔
88

89
            arguments.AddRange(options.AdditionalArguments);
187✔
90
            arguments.AddRange(normalizedIncludePaths.Select(x => $"-I{x}"));
187✔
91
            arguments.AddRange(normalizedSystemIncludePaths.Select(x => $"-isystem{x}"));
187✔
92
            arguments.AddRange(options.Defines.Select(x => $"-D{x}"));
784✔
93

94
            arguments.Add("-dM");
187✔
95
            arguments.Add("-E");
187✔
96

97
            switch (options.ParserKind)
187!
98
            {
99
                case CppParserKind.None:
100
                    break;
101
                case CppParserKind.Cpp:
102
                    arguments.Add("-xc++");
169✔
103
                    break;
169✔
104
                case CppParserKind.C:
105
                    arguments.Add("-xc");
×
106
                    break;
×
107
                case CppParserKind.ObjC:
108
                    arguments.Add("-xobjective-c");
18✔
109
                    break;
18✔
110
                default:
111
                    throw new ArgumentOutOfRangeException();
×
112
            }
113

114
            if (!arguments.Any(x => x.StartsWith("--target=")))
1,614✔
115
            {
116
                arguments.Add($"--target={GetTripleFromOptions(options)}");
187✔
117
            }
118

119
            if (options.ParseComments)
187✔
120
            {
121
                arguments.Add("-fparse-all-comments");
187✔
122
            }
123

124
            var translationFlags = CXTranslationUnit_Flags.CXTranslationUnit_None;
187✔
125
            translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_SkipFunctionBodies;                   // Don't traverse function bodies
187✔
126
            translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_IncludeAttributedTypes;               // Include attributed types in CXType
187✔
127
            translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_VisitImplicitAttributes;              // Implicit attributes should be visited
187✔
128

129
            if (options.ParseMacros)
187✔
130
            {
131
                translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_DetailedPreprocessingRecord;
2✔
132
            }
133
            translationFlags |= CXTranslationUnit_Flags.CXTranslationUnit_DetailedPreprocessingRecord;
187✔
134

135
            var argumentsArray = arguments.ToArray();
187✔
136

137
            using (var createIndex = CXIndex.Create())
187✔
138
            {
139
                var builder = new CppModelBuilder
187✔
140
                {
187✔
141
                    AutoSquashTypedef = options.AutoSquashTypedef,
187✔
142
                    ParseSystemIncludes = options.ParseSystemIncludes,
187✔
143
                    ParseTokenAttributeEnabled = options.ParseTokenAttributes,
187✔
144
                    ParseCommentAttributeEnabled = options.ParseCommentAttribute,
187✔
145
                };
187✔
146
                var compilation = builder.RootCompilation;
187✔
147

148
                string rootFileName = CppAstRootFileName;
187✔
149
                string rootFileContent = null;
187✔
150

151
                // Build the root input source file
152
                var tempBuilder = new StringBuilder();
187✔
153
                if (options.PreHeaderText != null)
187!
154
                {
155
                    tempBuilder.AppendLine(options.PreHeaderText);
×
156
                }
157

158
                foreach (var file in cppFiles)
748✔
159
                {
160
                    if (file.Content != null)
187✔
161
                    {
162
                        tempBuilder.AppendLine(file.Content);
94✔
163
                    }
164
                    else
165
                    {
166
                        var filePath = Path.Combine(Environment.CurrentDirectory, file.Filename);
93✔
167
                        tempBuilder.AppendLine($"#include \"{filePath}\"");
93✔
168
                    }
169
                }
170

171
                if (options.PostHeaderText != null)
187!
172
                {
173
                    tempBuilder.AppendLine(options.PostHeaderText);
×
174
                }
175

176
                // TODO: Add debug
177
                rootFileContent = tempBuilder.ToString();
187✔
178

179
                compilation.InputText = rootFileContent;
187✔
180

181
                CXTranslationUnit translationUnit;
182
                using (CXUnsavedFile unsavedFile = CXUnsavedFile.Create(rootFileName, rootFileContent))
187✔
183
                {
184
                    ReadOnlySpan<CXUnsavedFile> unsavedFiles = stackalloc CXUnsavedFile[] { unsavedFile };
187✔
185

186
                    translationUnit = CXTranslationUnit.Parse(createIndex
187✔
187
                        , rootFileName
187✔
188
                        , argumentsArray
187✔
189
                        , unsavedFiles
187✔
190
                        , translationFlags);
187✔
191
                }
187✔
192

193
                bool skipProcessing = false;
187✔
194

195
                if (translationUnit.NumDiagnostics != 0)
187✔
196
                {
197
                    for (uint i = 0; i < translationUnit.NumDiagnostics; ++i)
772✔
198
                    {
199
                        using (var diagnostic = translationUnit.GetDiagnostic(i))
199✔
200
                        {
201
                            var message = GetMessageAndLocation(rootFileContent, diagnostic, out var location);
199✔
202

203
                            switch (diagnostic.Severity)
199!
204
                            {
205
                                case CXDiagnosticSeverity.CXDiagnostic_Ignored:
206
                                case CXDiagnosticSeverity.CXDiagnostic_Note:
207
                                    compilation.Diagnostics.Info(message, location);
×
208
                                    break;
×
209
                                case CXDiagnosticSeverity.CXDiagnostic_Warning:
210
                                    // Avoid warning from clang (0, 0): warning: argument unused during compilation: '-fsyntax-only'
211
                                    if (!message.Contains("-fsyntax-only"))
199✔
212
                                    {
213
                                        compilation.Diagnostics.Warning(message, location);
12✔
214
                                    }
215
                                    break;
12✔
216
                                case CXDiagnosticSeverity.CXDiagnostic_Error:
217
                                case CXDiagnosticSeverity.CXDiagnostic_Fatal:
218
                                    compilation.Diagnostics.Error(message, location);
×
219
                                    skipProcessing = true;
×
220
                                    break;
221
                            }
222
                        }
187✔
223
                    }
224
                }
225

226
                if (skipProcessing)
187!
227
                {
228
                    compilation.Diagnostics.Warning($"Compilation aborted due to one or more errors listed above.", new CppSourceLocation(rootFileName, 0, 1, 1));
×
229
                }
230
                else
231
                {
232
                    translationUnit.Cursor.VisitChildren(builder.VisitTranslationUnit, clientData: default);
187✔
233
                }
234

235
                translationUnit.Dispose();
187✔
236

237
                return compilation;
187✔
238
            }
239
        }
187✔
240

241
        private static string GetMessageAndLocation(string rootContent, CXDiagnostic diagnostic, out CppSourceLocation location)
242
        {
243
            var builder = new StringBuilder();
199✔
244
            builder.Append(diagnostic.ToString());
199✔
245
            location = CppModelBuilder.GetSourceLocation(diagnostic.Location);
199✔
246
            if (location.File == CppAstRootFileName)
199!
247
            {
UNCOV
248
                var reader = new StringReader(rootContent);
×
UNCOV
249
                var lines = new List<string>();
×
250
                string line;
UNCOV
251
                while ((line = reader.ReadLine()) != null)
×
252
                {
UNCOV
253
                    lines.Add(line);
×
254
                }
255

UNCOV
256
                var lineIndex = location.Line - 1;
×
UNCOV
257
                if (lineIndex < lines.Count)
×
258
                {
UNCOV
259
                    builder.AppendLine();
×
UNCOV
260
                    builder.AppendLine(lines[lineIndex]);
×
UNCOV
261
                    for (int i = 0; i < location.Column - 1; i++)
×
262
                    {
UNCOV
263
                        builder.Append(i + 1 == location.Column - 1 ? "-" : " ");
×
264
                    }
265

UNCOV
266
                    builder.AppendLine("^-");
×
267
                }
268
            }
269

270
            diagnostic.Dispose();
199✔
271
            return builder.ToString();
199✔
272
        }
273

274
        private static string GetTripleFromOptions(CppParserOptions options)
275
        {
276
            // From https://clang.llvm.org/docs/CrossCompilation.html
277
            // <arch><sub>-<vendor>-<sys>-<abi>
278
            var targetCpu = GetTargetCpuAsString(options.TargetCpu);
187✔
279
            var targetCpuSub = options.TargetCpuSub ?? string.Empty;
187!
280
            var targetVendor = options.TargetVendor ?? "pc";
187!
281
            var targetSystem = options.TargetSystem ?? "windows";
187!
282
            var targetAbi = options.TargetAbi ?? "";
187!
283

284
            return $"{targetCpu}{targetCpuSub}-{targetVendor}-{targetSystem}-{targetAbi}";
187✔
285
        }
286

287
        private static string GetTargetCpuAsString(CppTargetCpu targetCpu)
288
        {
289
            switch (targetCpu)
290
            {
291
                case CppTargetCpu.X86:
292
                    return "i686";
12!
293
                case CppTargetCpu.X86_64:
294
                    return "x86_64";
157✔
295
                case CppTargetCpu.ARM:
296
                    return "arm";
×
297
                case CppTargetCpu.ARM64:
298
                    return "aarch64";
18✔
299
                default:
300
                    throw new ArgumentOutOfRangeException(nameof(targetCpu), targetCpu, null);
×
301
            }
302
        }
303

304
        private struct CppFileOrString
305
        {
306
            public string Filename;
307

308
            public string Content;
309

310
            public override string ToString()
311
            {
312
                return $"{nameof(Filename)}: {Filename}, {nameof(Content)}: {Content}";
×
313
            }
314
        }
315
    }
316
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc