7676
7777public class CxxPreprocessor extends Preprocessor {
7878
79- private static final String CPLUSPLUS = "__cplusplus" ;
8079 private static final String EVALUATED_TO_FALSE = "[{}:{}]: '{}' evaluated to false, skipping tokens that follow" ;
8180 private static final String MISSING_INCLUDE_MSG
8281 = "Preprocessor: {} include directive error(s). This is only relevant if parser creates syntax errors."
@@ -90,8 +89,55 @@ public class CxxPreprocessor extends Preprocessor {
9089 private File currentContextFile ;
9190
9291 private final Parser <Grammar > pplineParser ;
92+ /**
93+ * Contains a standard set of pre-compiled macros, which are defined for each
94+ * compilation unit. This map is used if there is no specific compilation unit
95+ * settings. The map contains
96+ * <ol>
97+ * <li>{@link Macro#STANDARD_MACROS}</li>
98+ * <li>{@link CxxConfiguration#getDefines()} and</li>
99+ * <li>forced includes (see
100+ * {@link CxxConfiguration#getForceIncludeFiles()})</li>
101+ * </ol>
102+ * All hi-prio macros are pre-parsed while construction of
103+ * {@link CxxPreprocessor}
104+ */
93105 private final MapChain <String , Macro > fixedMacros = new MapChain <>();
106+ /**
107+ * If CxxConfiguration contains any compilation unit settings, this map is
108+ * filled with a set of pre-compiled macros. Those macros must be defined for
109+ * each compilation unit:
110+ * <ol>
111+ * <li>{@link Macro#UNIT_MACROS}</li>
112+ * <li>{@link CxxConfiguration#getDefines()} and</li>
113+ * <li>forced includes (see
114+ * {@link CxxConfiguration#getForceIncludeFiles()})</li>
115+ * </ol>
116+ * The map is immutable; macros are pre-parsed while construction of
117+ * {@link CxxPreprocessor}
118+ */
119+ private final Map <String , Macro > predefinedUnitMacros ;
120+ /**
121+ * If current processed file has some specific configuration settings, this
122+ * map will be filled with relevant macros and defines:
123+ * <ol>
124+ * <li>predefined compilation unit macros will be added (see
125+ * {@link CxxPreprocessor#predefinedUnitMacros}</li>
126+ * <li>specific unit settings will be parsed and added (see
127+ * {@link CxxConfiguration#getCompilationUnitSettings(String)}</li>
128+ * </ol>
129+ * Map is recalculated each time {@link CxxPreprocessor} is about to analyze a
130+ * new file (see {@link CxxPreprocessor#init()}.
131+ *
132+ * If current processed file has no specific configuration settings -
133+ * {@link CxxPreprocessor#fixedMacros} will be used.
134+ */
94135 private MapChain <String , Macro > unitMacros ;
136+ /**
137+ * Pre-parsed defines from the global compilation unit settings, see
138+ * {@link CxxConfiguration#getGlobalCompilationUnitSettings()}.
139+ */
140+ private final Map <String , Macro > globalUnitMacros ;
95141 private final Set <File > analysedFiles = new HashSet <>();
96142 private final SourceCodeProvider codeProvider ;
97143 private SourceCodeProvider unitCodeProvider ;
@@ -127,36 +173,82 @@ public CxxPreprocessor(SquidAstVisitorContext<Grammar> context,
127173
128174 pplineParser = CppParser .create (conf );
129175
176+ final Map <String , Macro > configuredMacros = parseConfiguredMacros ();
177+ fillFixedMacros (configuredMacros );
178+ predefinedUnitMacros = parsePredefinedUnitMacros (configuredMacros );
179+ globalUnitMacros = parseGlobalUnitMacros ();
180+
181+ ctorInProgress = false ;
182+ }
183+
184+ private Map <String , Macro > parseConfiguredMacros () {
185+ final List <String > configuredDefines = conf .getDefines ();
186+ if (configuredDefines .isEmpty ()) {
187+ return Collections .emptyMap ();
188+ }
189+ LOG .debug ("parsing configured defines" );
190+ return parseMacroDefinitions (configuredDefines );
191+ }
192+
193+ private void fillFixedMacros (Map <String , Macro > configuredMacros ) {
194+ if (!ctorInProgress || (getMacros () != fixedMacros ) || !fixedMacros .getHighPrioMap ().isEmpty ()) {
195+ throw new IllegalStateException ("Preconditions for initial fill-out of fixedMacros were violated" );
196+ }
197+
130198 try {
131199 getMacros ().setHighPrio (true );
200+ getMacros ().putAll (Macro .STANDARD_MACROS );
201+ getMacros ().putAll (configuredMacros );
202+ parseForcedIncludes ();
203+ } finally {
204+ getMacros ().setHighPrio (false );
205+ }
206+ }
132207
133- // parse the configured defines and store into the macro library
134- for (String define : conf .getDefines ()) {
135- LOG .debug ("parsing external macro: '{}'" , define );
136- if (!"" .equals (define )) {
137- Macro macro = parseMacroDefinition ("#define " + define );
138- LOG .debug ("storing external macro: '{}'" , macro );
139- getMacros ().put (macro .name , macro );
140- }
141- }
208+ /**
209+ * Create temporary unitMacros map; This map will be used as an active macros'
210+ * storage while parsing of forced includes. After parsing was over extract
211+ * resulting macros and destroy the unitMacros. fixedMacros will be set as
212+ * active macros again.
213+ */
214+ private Map <String , Macro > parsePredefinedUnitMacros (Map <String , Macro > configuredMacros ) {
215+ if (!ctorInProgress || (unitMacros != null )) {
216+ throw new IllegalStateException ("Preconditions for initial fill-out of predefinedUnitMacros were violated" );
217+ }
142218
143- // set standard macros
144- getMacros ().putAll (Macro .STANDARD_MACROS );
219+ if (conf .getCompilationUnitSourceFiles ().isEmpty () && (conf .getGlobalCompilationUnitSettings () == null )) {
220+ // configuration doesn't contain any settings for compilation units.
221+ // CxxPreprocessor will use fixedMacros only
222+ return Collections .emptyMap ();
223+ }
145224
146- // parse the configured force includes and store into the macro library
147- for (String include : conf .getForceIncludeFiles ()) {
148- LOG .debug ("parsing force include: '{}'" , include );
149- if (!"" .equals (include )) {
150- parseIncludeLine ("#include \" " + include + "\" " , "sonar." + this .language .getPropertiesKey ()
151- + ".forceIncludes" , conf .getEncoding ());
152- }
153- }
225+ unitMacros = new MapChain <>();
226+ if (getMacros () != unitMacros ) {
227+ throw new IllegalStateException ("expected unitMacros as active macros map" );
228+ }
229+
230+ try {
231+ getMacros ().setHighPrio (true );
232+ getMacros ().putAll (Macro .UNIT_MACROS );
233+ getMacros ().putAll (configuredMacros );
234+ parseForcedIncludes ();
235+ final HashMap <String , Macro > result = new HashMap <>(unitMacros .getHighPrioMap ());
236+ return result ;
154237 } finally {
155- getMacros ().setHighPrio (false );
156- ctorInProgress = false ;
238+ getMacros ().setHighPrio (false ); // just for the symmetry
239+ unitMacros = null ; // remove unitMacros, switch getMacros() to fixedMacros
157240 }
158241 }
159242
243+ private Map <String , Macro > parseGlobalUnitMacros () {
244+ final CxxCompilationUnitSettings globalCUSettings = conf .getGlobalCompilationUnitSettings ();
245+ if (globalCUSettings == null ) {
246+ return Collections .emptyMap ();
247+ }
248+ LOG .debug ("parsing global compilation unit defines" );
249+ return parseMacroDefinitions (globalCUSettings .getDefines ());
250+ }
251+
160252 public static void finalReport () {
161253 if (missingIncludeFilesCounter != 0 ) {
162254 LOG .warn (MISSING_INCLUDE_MSG , missingIncludeFilesCounter );
@@ -457,14 +549,14 @@ public void init() {
457549 Objects .requireNonNull (context .getFile (), "SquidAstVisitorContext::getFile() must be non-null" );
458550 compilationUnitSettings = conf .getCompilationUnitSettings (currentContextFile .getAbsolutePath ());
459551
552+ boolean useGlobalCUSettings = false ;
553+
460554 if (compilationUnitSettings != null ) {
461555 LOG .debug ("compilation unit settings for: '{}'" , currentContextFile );
462- } else {
556+ } else if ( conf . getGlobalCompilationUnitSettings () != null ) {
463557 compilationUnitSettings = conf .getGlobalCompilationUnitSettings ();
464-
465- if (compilationUnitSettings != null ) {
466- LOG .debug ("global compilation unit settings for: '{}'" , currentContextFile );
467- }
558+ useGlobalCUSettings = true ;
559+ LOG .debug ("global compilation unit settings for: '{}'" , currentContextFile );
468560 }
469561
470562 if (compilationUnitSettings != null ) {
@@ -478,54 +570,32 @@ public void init() {
478570 // Treat all global defines as high prio
479571 getMacros ().setHighPrio (true );
480572
481- // parse the configured defines and store into the macro library
482- for (String define : conf .getDefines ()) {
483- LOG .debug ("parsing external macro to unit: '{}'" , define );
484- if (!"" .equals (define )) {
485- Macro macro = parseMacroDefinition ("#define " + define );
486- if (macro != null ) {
487- LOG .debug ("storing external macro to unit: '{}'" , macro );
488- getMacros ().put (macro .name , macro );
489- }
490- }
491- }
492-
493- // set standard macros
494- // using smaller set of defines as rest is provides by compilation unit settings
495- getMacros ().putAll (Macro .UNIT_MACROS );
496-
497- // parse the configured force includes and store into the macro library
498- for (String include : conf .getForceIncludeFiles ()) {
499- LOG .debug ("parsing force include to unit: '{}'" , include );
500- if (!"" .equals (include )) {
501- // TODO -> this needs to come from language
502- parseIncludeLine ("#include \" " + include + "\" " , "sonar.cxx.forceIncludes" , conf .getEncoding ());
503- }
504- }
573+ // add macros which are predefined for each compilation unit
574+ getMacros ().putAll (predefinedUnitMacros );
505575
506576 // rest of defines comes from compilation unit settings
507- for ( Map . Entry < String , String > entry : compilationUnitSettings . getDefines (). entrySet () ) {
508- final String name = entry . getKey ( );
509- final String body = entry . getValue ();
510- getMacros ().put ( name , new Macro ( name , body ));
577+ if ( useGlobalCUSettings ) {
578+ getMacros (). putAll ( globalUnitMacros );
579+ } else {
580+ getMacros ().putAll ( parseMacroDefinitions ( compilationUnitSettings . getDefines () ));
511581 }
512582 } finally {
513583 getMacros ().setHighPrio (false );
514584 }
515585
516- if (getMacro (CPLUSPLUS ) == null ) {
586+ if (getMacro (Macro . CPLUSPLUS ) == null ) {
517587 //Create macros to replace C++ keywords when parsing C files
518588 getMacros ().putAll (Macro .COMPATIBILITY_MACROS );
519589 }
520590 } else {
521591 // Use global settings
522592 LOG .debug ("global settings for: '{}'" , currentContextFile );
523- if (isCFile (currentContextFile .getAbsolutePath ())) {
593+ if (isCFile (currentContextFile .getName ())) {
524594 //Create macros to replace C++ keywords when parsing C files
525595 getMacros ().putAll (Macro .COMPATIBILITY_MACROS );
526- fixedMacros .disable (CPLUSPLUS );
596+ getMacros () .disable (Macro . CPLUSPLUS );
527597 } else {
528- fixedMacros .enable (CPLUSPLUS );
598+ getMacros () .enable (Macro . CPLUSPLUS );
529599 }
530600 }
531601 }
@@ -710,6 +780,21 @@ private int expandFunctionLikeMacro(String macroName, List<Token> restTokens, Li
710780 return tokensConsumedMatchingArgs ;
711781 }
712782
783+ /**
784+ * Parse the configured forced includes and store into the macro library.
785+ * Current macro library depends on the return value of
786+ * CxxPreprocessor#getMacros()
787+ */
788+ private void parseForcedIncludes () {
789+ for (String include : conf .getForceIncludeFiles ()) {
790+ if (!include .isEmpty ()) {
791+ LOG .debug ("parsing force include: '{}'" , include );
792+ parseIncludeLine ("#include \" " + include + "\" " , "sonar." + this .language .getPropertiesKey () + ".forceIncludes" ,
793+ conf .getEncoding ());
794+ }
795+ }
796+ }
797+
713798 private List <Token > expandMacro (String macroName , String macroExpression ) {
714799 // C++ standard 16.3.4/2 Macro Replacement - Rescanning and further replacement
715800 List <Token > tokens = null ;
@@ -855,6 +940,42 @@ private Macro parseMacroDefinition(String macroDef) {
855940 .getFirstDescendant (CppGrammar .defineLine ));
856941 }
857942
943+ /**
944+ * Parse defines spited into key-value format
945+ * (sonar.cxx.jsonCompilationDatabase)
946+ */
947+ private Map <String , Macro > parseMacroDefinitions (Map <String , String > defines ) {
948+ final List <String > margedDefines = defines .entrySet ().stream ().map (e -> e .getKey () + " " + e .getValue ())
949+ .collect (Collectors .toList ());
950+ return parseMacroDefinitions (margedDefines );
951+ }
952+
953+ /**
954+ * Parse defines, which are merged into one string (see sonar.cxx.defines)
955+ */
956+ private Map <String , Macro > parseMacroDefinitions (List <String > defines ) {
957+ final Map <String , Macro > result = new HashMap <>();
958+
959+ for (String define : defines ) {
960+ if (define .isEmpty ()) {
961+ continue ;
962+ }
963+
964+ final String defineString = "#define " + define ;
965+
966+ LOG .debug ("parsing external macro: '{}'" , defineString );
967+ final Macro macro = parseMacroDefinition (defineString );
968+
969+ if (macro != null ) {
970+ if (LOG .isDebugEnabled ()) {
971+ LOG .debug ("storing external macro: '{}'" , macro );
972+ }
973+ result .put (macro .name , macro );
974+ }
975+ }
976+ return result ;
977+ }
978+
858979 private File findIncludedFile (AstNode ast , Token token , String currFileName ) {
859980 String includedFileName = null ;
860981 boolean quoted = false ;
0 commit comments