Skip to content

Commit c910f95

Browse files
authored
Merge pull request SonarOpenCommunity#2406 from guwirth/pp-refactoring-14
support Feature Checking Macros
2 parents c5229cd + d94614f commit c910f95

File tree

6 files changed

+196
-29
lines changed

6 files changed

+196
-29
lines changed

cxx-squid/src/main/java/org/sonar/cxx/preprocessor/PPExpression.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,14 @@ private BigInteger evalLeaf(AstNode exprAst) {
220220
if (!macroEvaluationStack.contains(id)) {
221221
PPMacro macro = pp.getMacro(id);
222222
if (macro != null) {
223-
macroEvaluationStack.push(id);
224-
result = evalToInt(TokenUtils.merge(macro.replacementList), exprAst);
225-
macroEvaluationStack.pop();
223+
if (macro.replacementList.size() == 1 && macro.replacementList.get(0).getValue().equals(macro.identifier)) {
224+
// special case, self-referencing macro, e.g. __has_include=__has_include
225+
result = BigInteger.ONE;
226+
} else {
227+
macroEvaluationStack.push(id);
228+
result = evalToInt(TokenUtils.merge(macro.replacementList), exprAst);
229+
macroEvaluationStack.pop();
230+
}
226231
}
227232
} else {
228233
LOG.debug("preprocessor: self-referential macro '{}' detected;"

cxx-squid/src/main/java/org/sonar/cxx/preprocessor/PPPredefinedMacros.java

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
final class PPPredefinedMacros {
2828

2929
private static final String[] predefinedMacros = {
30+
//
31+
// C++
32+
//
3033
"__FILE__ \"file\"",
3134
"__LINE__ 1",
3235
// indicates 'date unknown'. should suffice
@@ -37,8 +40,23 @@ final class PPPredefinedMacros {
3740
"__STDC_HOSTED__ 1",
3841
// set C++14 as default
3942
"__cplusplus 201402L",
43+
//
4044
// __has_include support (C++17)
41-
"__has_include 1"
45+
//
46+
"__has_include __has_include", // define __has_include as macro, for e.g. #if __has_include
47+
"__has_include_next __has_include_next", // define __has_include as macro, for e.g. #if __has_include
48+
//
49+
// source: https://clang.llvm.org/docs/LanguageExtensions.html
50+
//
51+
"__has_builtin(x) 0",
52+
"__has_feature(x) 0",
53+
"__has_extension(x) 0",
54+
"__has_cpp_attribute(x) 0",
55+
"__has_c_attribute(x) 0",
56+
"__has_attribute(x) 0",
57+
"__has_declspec_attribute(x) 0",
58+
"__is_identifier(x) 1",
59+
"__has_warning(x) 0"
4260
};
4361

4462
private PPPredefinedMacros() {

cxx-squid/src/main/java/org/sonar/cxx/preprocessor/PPReplace.java

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,6 @@
2525
import com.sonar.cxx.sslr.impl.token.TokenUtils;
2626
import java.util.ArrayList;
2727
import java.util.List;
28-
import org.sonar.api.utils.log.Logger;
29-
import org.sonar.api.utils.log.Loggers;
3028
import org.sonar.cxx.parser.CxxKeyword;
3129
import org.sonar.cxx.parser.CxxTokenType;
3230

@@ -36,7 +34,6 @@
3634
*/
3735
class PPReplace {
3836

39-
private static final Logger LOG = Loggers.get(PPReplace.class);
4037
private final CxxPreprocessor pp;
4138

4239
PPReplace(CxxPreprocessor pp) {
@@ -95,6 +92,7 @@ int replaceFunctionLikeMacro(PPMacro macro, List<Token> restTokens, List<Token>
9592
* the replacement-list. The sequence is terminated by the matching ) token, skipping intervening matched pairs of
9693
* left and right parentheses.
9794
*/
95+
@SuppressWarnings({"java:S3776", "java:S1541"})
9896
private static int extractArguments(List<Token> tokens, List<Token> arguments) {
9997
// argument list must start with '('
10098
int size = tokens.size();
@@ -149,7 +147,6 @@ private static int extractArguments(List<Token> tokens, List<Token> arguments) {
149147
}
150148
}
151149

152-
LOG.error("preprocessor 'matchArguments' error, missing ')': {}", tokens.toString());
153150
return 0;
154151
}
155152

@@ -177,6 +174,7 @@ private List<Token> replaceParams(PPMacro macro, List<Token> arguments) {
177174
* identifiers (which are not macro-expanded first) and then concatenates the result.
178175
*
179176
*/
177+
@SuppressWarnings({"java:S3776"})
180178
private void handleOperators(List<Token> replacementList, List<String> parameters, List<Token> arguments,
181179
List<Token> result) {
182180

@@ -196,22 +194,20 @@ private void handleOperators(List<Token> replacementList, List<String> parameter
196194
//
197195
// not a token to be replaced by a macro argument
198196
//
199-
if ((i = handleVaOpt(view, parameters, arguments, result)) <= 0) {
200-
if ((i = handleConcatenation(view, parameters, arguments, result)) <= 0) {
201-
result.add(token);
202-
}
197+
if (((i = handleVaOpt(view, parameters, arguments, result)) <= 0)
198+
&& ((i = handleConcatenation(view, parameters, arguments, result)) <= 0)) {
199+
result.add(token);
203200
}
204201
} else if (parameterIndex < arguments.size()) {
205202
//
206203
// token to be replaced by a macro argument
207204
//
208205
argument = arguments.get(parameterIndex);
209206

210-
if ((i = handleConcatenation(view, parameters, arguments, result)) <= 0) {
211-
if (tokensConsumed < 1 || !handleStringification(
212-
replacementList.subList(tokensConsumed - 1, replacementList.size()), argument, result)) {
213-
newValue = expand(argument.getValue());
214-
}
207+
if (((i = handleConcatenation(view, parameters, arguments, result)) <= 0)
208+
&& (tokensConsumed < 1 || !handleStringification(
209+
replacementList.subList(tokensConsumed - 1, replacementList.size()), argument, result))) {
210+
newValue = expand(argument.getValue());
215211
}
216212
}
217213

@@ -259,8 +255,8 @@ private static Token getReplacementToken(Token token, List<String> parameters, L
259255
* (1) A ## ## B == A ## B
260256
* (2) A ## B ## C ...
261257
*/
262-
private int handleConcatenation(List<Token> replacementList, List<String> parameters, List<Token> arguments,
263-
List<Token> result) {
258+
private static int handleConcatenation(List<Token> replacementList, List<String> parameters, List<Token> arguments,
259+
List<Token> result) {
264260

265261
int tokensConsumed = 0;
266262

@@ -288,7 +284,7 @@ && isIdentifier(replacementList.get(tokensConsumed).getType())
288284
* In function-like macros, a # operator before an identifier in the argument-list runs the identifier through
289285
* parameter argument and encloses the result in quotes, effectively creating a string literal.
290286
*/
291-
private boolean handleStringification(List<Token> replacementList, Token argument, List<Token> result) {
287+
private static boolean handleStringification(List<Token> replacementList, Token argument, List<Token> result) {
292288
if (PPPunctuator.HASH.equals(replacementList.get(0).getType())) {
293289
result.set(result.size() - 1,
294290
PPGeneratedToken.build(argument, argument.getType(),
@@ -307,7 +303,7 @@ private boolean handleStringification(List<Token> replacementList, Token argumen
307303
* the ## does nothing when the variable arguments are present, but removes the comma when the variable arguments are
308304
* not present: this makes it possible to define macros such as fprintf (stderr, format, ##__VA_ARGS__).
309305
*/
310-
private void handleEmptyVaArgs(List<Token> replacementList, List<Token> result) {
306+
private static void handleEmptyVaArgs(List<Token> replacementList, List<Token> result) {
311307
if (!"__VA_ARGS__".equals(replacementList.get(0).getValue())) {
312308
return;
313309
}
@@ -329,6 +325,8 @@ private void handleEmptyVaArgs(List<Token> replacementList, List<Token> result)
329325
case COMMA: // (2)
330326
result.remove(lastIndex);
331327
break;
328+
default:
329+
break;
332330
}
333331
}
334332
}
@@ -344,6 +342,7 @@ private void handleEmptyVaArgs(List<Token> replacementList, List<Token> result)
344342
* __VA_OPT__ ( pp-tokensopt )
345343
* </code>
346344
*/
345+
@SuppressWarnings({"java:S3776", "java:S1142"})
347346
private int handleVaOpt(List<Token> replacementList, List<String> parameters, List<Token> arguments,
348347
List<Token> result) {
349348
var firstIndex = -1;
@@ -370,21 +369,21 @@ private int handleVaOpt(List<Token> replacementList, List<String> parameters, Li
370369
}
371370
}
372371

372+
int consumedTokens = 0;
373+
373374
if (firstIndex != -1 && lastIndex != -1) {
374375
if (parameters.size() == arguments.size()) {
375376
// __VA_OPT__ ( pp-tokensopt ), keep pp-tokensopt
376377
var ppTokens = replacementList.subList(firstIndex + 1, lastIndex);
377378
handleOperators(ppTokens, parameters, arguments, result);
378-
return 2 + ppTokens.size();
379+
consumedTokens = 2 + ppTokens.size();
379380
} else {
380381
// remove __VA_OPT__ ( pp-tokensopt )
381-
return 1 + lastIndex - firstIndex;
382+
consumedTokens = 1 + lastIndex - firstIndex;
382383
}
383384
}
384385

385-
LOG.error("preprocessor '__VA_OPT__* error: {}:{}",
386-
replacementList.get(0).getLine(), replacementList.get(0).getColumn()); // todo
387-
return 0;
386+
return consumedTokens;
388387
}
389388

390389
}

cxx-squid/src/test/java/org/sonar/cxx/parser/PreprocessorDirectivesTest.java

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,4 +560,127 @@ void has_include() {
560560
.isEqualTo("r = 0 ; EOF");
561561
}
562562

563+
@Test
564+
void featureCheckingMacros() {
565+
//
566+
// source: https://clang.llvm.org/docs/LanguageExtensions.html
567+
//
568+
assertThat(parse(
569+
"#ifdef __has_builtin\n"
570+
+ "# define OK 1\n"
571+
+ "#else\n"
572+
+ "# define OK 0\n"
573+
+ "#endif\n"
574+
+ "r = OK;"))
575+
.isEqualTo("r = 1 ; EOF");
576+
577+
assertThat(parse(
578+
"#ifdef __has_feature\n"
579+
+ "# define OK 1\n"
580+
+ "#else\n"
581+
+ "# define OK 0\n"
582+
+ "#endif\n"
583+
+ "r = OK;"))
584+
.isEqualTo("r = 1 ; EOF");
585+
586+
assertThat(parse(
587+
"#ifdef __has_extension\n"
588+
+ "# define OK 1\n"
589+
+ "#else\n"
590+
+ "# define OK 0\n"
591+
+ "#endif\n"
592+
+ "r = OK;"))
593+
.isEqualTo("r = 1 ; EOF");
594+
595+
assertThat(parse(
596+
"#ifdef __has_cpp_attribute\n"
597+
+ "# define OK 1\n"
598+
+ "#else\n"
599+
+ "# define OK 0\n"
600+
+ "#endif\n"
601+
+ "r = OK;"))
602+
.isEqualTo("r = 1 ; EOF");
603+
604+
assertThat(parse(
605+
"#ifdef __has_c_attribute\n"
606+
+ "# define OK 1\n"
607+
+ "#else\n"
608+
+ "# define OK 0\n"
609+
+ "#endif\n"
610+
+ "r = OK;"))
611+
.isEqualTo("r = 1 ; EOF");
612+
613+
assertThat(parse(
614+
"#ifdef __has_attribute\n"
615+
+ "# define OK 1\n"
616+
+ "#else\n"
617+
+ "# define OK 0\n"
618+
+ "#endif\n"
619+
+ "r = OK;"))
620+
.isEqualTo("r = 1 ; EOF");
621+
622+
assertThat(parse(
623+
"#ifdef __has_attribute\n"
624+
+ "# define OK 1\n"
625+
+ "#else\n"
626+
+ "# define OK 0\n"
627+
+ "#endif\n"
628+
+ "r = OK;"))
629+
.isEqualTo("r = 1 ; EOF");
630+
631+
assertThat(parse(
632+
"#ifdef __has_declspec_attribute\n"
633+
+ "# define OK 1\n"
634+
+ "#else\n"
635+
+ "# define OK 0\n"
636+
+ "#endif\n"
637+
+ "r = OK;"))
638+
.isEqualTo("r = 1 ; EOF");
639+
640+
assertThat(parse(
641+
"#ifdef __is_identifier\n"
642+
+ "# define OK 1\n"
643+
+ "#else\n"
644+
+ "# define OK 0\n"
645+
+ "#endif\n"
646+
+ "r = OK;"))
647+
.isEqualTo("r = 1 ; EOF");
648+
649+
assertThat(parse(
650+
"#ifdef __has_attribute\n"
651+
+ "# define OK 1\n"
652+
+ "#else\n"
653+
+ "# define OK 0\n"
654+
+ "#endif\n"
655+
+ "r = OK;"))
656+
.isEqualTo("r = 1 ; EOF");
657+
658+
assertThat(parse(
659+
"#ifdef __has_include\n"
660+
+ "# define OK 1\n"
661+
+ "#else\n"
662+
+ "# define OK 0\n"
663+
+ "#endif\n"
664+
+ "r = OK;"))
665+
.isEqualTo("r = 1 ; EOF");
666+
667+
assertThat(parse(
668+
"#ifdef __has_include_next\n"
669+
+ "# define OK 1\n"
670+
+ "#else\n"
671+
+ "# define OK 0\n"
672+
+ "#endif\n"
673+
+ "r = OK;"))
674+
.isEqualTo("r = 1 ; EOF");
675+
676+
assertThat(parse(
677+
"#ifdef __has_warning\n"
678+
+ "# define OK 1\n"
679+
+ "#else\n"
680+
+ "# define OK 0\n"
681+
+ "#endif\n"
682+
+ "r = OK;"))
683+
.isEqualTo("r = 1 ; EOF");
684+
}
685+
563686
}

cxx-squid/src/test/java/org/sonar/cxx/preprocessor/PPExpressionTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,17 @@ void std_macro_evaluated_as_expected() {
381381
assertThat(evaluate("__STDC__")).isTrue();
382382
assertThat(evaluate("__STDC_HOSTED__")).isTrue();
383383
assertThat(evaluate("__cplusplus")).isTrue();
384+
assertThat(evaluate("__has_builtin")).isFalse();
385+
assertThat(evaluate("__has_feature")).isFalse();
386+
assertThat(evaluate("__has_extension")).isFalse();
387+
assertThat(evaluate("__has_cpp_attribute")).isFalse();
388+
assertThat(evaluate("__has_c_attribute")).isFalse();
389+
assertThat(evaluate("__has_attribute")).isFalse();
390+
assertThat(evaluate("__has_declspec_attribute")).isFalse();
391+
assertThat(evaluate("__is_identifier")).isTrue();
384392
assertThat(evaluate("__has_include")).isTrue();
393+
assertThat(evaluate("__has_include_next")).isTrue();
394+
assertThat(evaluate("__has_warning")).isFalse();
385395
}
386396

387397
}

cxx-squid/src/test/java/org/sonar/cxx/preprocessor/PPPredefinedMacrosTest.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,24 @@ void testPredefinedMacroValues() {
3535
"__STDC__",
3636
"__STDC_HOSTED__",
3737
"__cplusplus",
38-
"__has_include"
38+
// __has_include support (C++17)
39+
"__has_include",
40+
"__has_include_next",
41+
// source: https://clang.llvm.org/docs/LanguageExtensions.html
42+
"__has_builtin",
43+
"__has_feature",
44+
"__has_extension",
45+
"__has_cpp_attribute",
46+
"__has_c_attribute",
47+
"__has_attribute",
48+
"__has_declspec_attribute",
49+
"__is_identifier",
50+
"__has_warning"
3951
);
4052
String[] result = PPPredefinedMacros.predefinedMacroValues();
4153
assertThat(result)
42-
.hasSize(8)
43-
.allMatch(s -> expResult.contains(s.split(" ")[0]));
54+
.hasSize(18)
55+
.allMatch(s -> expResult.contains(s.split("[^a-zA-Z_]")[0]));
4456
}
4557

4658
}

0 commit comments

Comments
 (0)