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

xoofx / CppAst.NET / 14764138022

30 Apr 2025 08:52PM UTC coverage: 78.596% (-0.2%) from 78.842%
14764138022

push

github

web-flow
Merge pull request #110 from xoofx/objective-c

Add support for Objective-C

1123 of 1824 branches covered (61.57%)

Branch coverage included in aggregate %.

456 of 566 new or added lines in 13 files covered. (80.57%)

2 existing lines in 1 file now uncovered.

4888 of 5824 relevant lines covered (83.93%)

2189.8 hits per line

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

82.56
/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));
93!
32
            var cppFiles = new List<CppFileOrString> { new CppFileOrString() { Filename = cppFilename, Content = cppText, } };
93✔
33
            return ParseInternal(cppFiles, options);
93✔
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));
92!
45
            var files = new List<string>() { cppFilename };
92✔
46
            return ParseFiles(files, options);
92✔
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));
92!
58

59
            var cppFiles = new List<CppFileOrString>();
92✔
60
            foreach (var cppFilepath in cppFilenameList)
368✔
61
            {
62
                if (string.IsNullOrEmpty(cppFilepath)) throw new InvalidOperationException("A null or empty filename is invalid in the list");
92!
63
                cppFiles.Add(new CppFileOrString() { Filename = cppFilepath });
92✔
64
            }
65
            return ParseInternal(cppFiles, options);
92✔
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));
185!
77

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

179
                compilation.InputText = rootFileContent;
185✔
180

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

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

193
                bool skipProcessing = false;
185✔
194

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

203
                            switch (diagnostic.Severity)
197!
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"))
197✔
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
                        }
185✔
223
                    }
224
                }
225

226
                if (skipProcessing)
185!
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);
185✔
233
                }
234

235
                translationUnit.Dispose();
185✔
236

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

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

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

266
                    builder.AppendLine("^-");
6✔
267
                }
268
            }
269

270
            diagnostic.Dispose();
197✔
271
            return builder.ToString();
197✔
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);
185✔
279
            var targetCpuSub = options.TargetCpuSub ?? string.Empty;
185!
280
            var targetVendor = options.TargetVendor ?? "pc";
185!
281
            var targetSystem = options.TargetSystem ?? "windows";
185!
282
            var targetAbi = options.TargetAbi ?? "";
185!
283

284
            return $"{targetCpu}{targetCpuSub}-{targetVendor}-{targetSystem}-{targetAbi}";
185✔
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";
16✔
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