diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/scanner/InclusionTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/scanner/InclusionTests.java index 1f81668a075..882a9b89077 100644 --- a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/scanner/InclusionTests.java +++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/core/parser/tests/scanner/InclusionTests.java @@ -16,10 +16,13 @@ package org.eclipse.cdt.core.parser.tests.scanner; import java.util.Collections; +import java.util.HashMap; +import java.util.Map; import org.eclipse.cdt.core.model.ICProject; import org.eclipse.cdt.core.parser.ExtendedScannerInfo; import org.eclipse.cdt.core.parser.FileContent; +import org.eclipse.cdt.core.parser.IProblem; import org.eclipse.cdt.core.parser.IScannerInfo; import org.eclipse.cdt.core.parser.IToken; import org.eclipse.cdt.core.parser.ParserLanguage; @@ -261,6 +264,136 @@ public class InclusionTests extends PreprocessorTestsBase { validateEOF(); } + // #if __has_include("does_not_exist.h") + // inactive + // #endif + // #if !__has_include("does_not_exist.h") + // identifier + // #endif + // + // #if __has_include("test.h") + // identifier2 + // #endif + // #if !__has_include("test.h") + // inactive + // #endif + // + // #if __has_include() + // identifier3 + // #endif + // + // #define MACRO __has_include("test.h") + // #if MACRO + // identifier4 + // #endif + // + // #define MACRO2 __has_include() + // #if MACRO2 + // identifier5 + // #endif + // + // #define HEADER_NAME "test.h" + // #define MACRO3 __has_include(HEADER_NAME) + // #if MACRO3 + // identifier6 + // #endif + // + // //Note: This one works with Clang and MSVC but not GCC. + // #define HEADER_NAME2 ("test.h") + // #define MACRO4 __has_include HEADER_NAME2 + // #if MACRO4 + // identifier7 + // #endif + // + // #ifdef __has_include + // identifier8 + // #endif + // + // #if defined(__has_include) + // identifier9 + // #endif + public void testHasInclude() throws Exception { + importFile("test.h", ""); + IFile base = importFile("test.cpp", getAboveComment()); + IScannerInfo scannerInfo = new ExtendedScannerInfo(Collections.EMPTY_MAP, + new String[] { fProject.getProject().getLocation().toOSString() }, new String[] {}, null); + FileContent reader = FileContent.create(base); + initializeScanner(reader, ParserLanguage.CPP, ParserMode.COMPLETE_PARSE, scannerInfo); + validateIdentifier("identifier"); + validateIdentifier("identifier2"); + validateIdentifier("identifier3"); + validateIdentifier("identifier4"); + validateIdentifier("identifier5"); + validateIdentifier("identifier6"); + validateIdentifier("identifier7"); + validateIdentifier("identifier8"); + validateIdentifier("identifier9"); + validateEOF(); + } + + // #include "foo.h" + // + // #ifdef __has_include_next + // identifier4 + // #endif + // + // #if defined(__has_include_next) + // identifier5 + // #endif + + // identifier + // #include "intermed.h" + + // identifier2 + // #if __has_include_next() + // #include_next + // #endif + + // identifier3 + public void testHasIncludeNext() throws Exception { + StringBuilder[] sections = getTestContent(4); + String baseFile = sections[0].toString(); //$NON-NLS-1$ + String foo1 = sections[1].toString(); //$NON-NLS-1$ + String intermed = sections[2].toString(); //$NON-NLS-1$ + String foo2 = sections[3].toString(); //$NON-NLS-1$ + + IFolder one = importFolder("one"); //$NON-NLS-1$ + IFolder two = importFolder("two"); //$NON-NLS-1$ + IFile base = importFile("base.cpp", baseFile); //$NON-NLS-1$ + importFile("one/foo.h", foo1); //$NON-NLS-1$ + importFile("one/intermed.h", intermed); //$NON-NLS-1$ + importFile("two/foo.h", foo2); //$NON-NLS-1$ + + String[] path = new String[2]; + path[0] = one.getLocation().toOSString(); + path[1] = two.getLocation().toOSString(); + + Map definedSymbols = new HashMap<>(); + definedSymbols.put("__GNUC__", "5"); + definedSymbols.put("__GNUC_MINOR__", "0"); + + IScannerInfo scannerInfo = new ExtendedScannerInfo(definedSymbols, path, new String[] {}, null); + FileContent reader = FileContent.create(base); + initializeScanner(reader, ParserLanguage.CPP, ParserMode.COMPLETE_PARSE, scannerInfo); + validateIdentifier("identifier"); + validateIdentifier("identifier2"); + validateIdentifier("identifier3"); + validateIdentifier("identifier4"); + validateIdentifier("identifier5"); + } + + // void foo() { + // __has_include; + // } + public void testHasIncludeProblem() throws Exception { + IFile base = importFile("test.cpp", getAboveComment()); + IScannerInfo scannerInfo = new ExtendedScannerInfo(Collections.EMPTY_MAP, null, new String[] {}, null); + FileContent reader = FileContent.create(base); + initializeScanner(reader, ParserLanguage.CPP, ParserMode.COMPLETE_PARSE, scannerInfo); + fullyTokenize(); + validateProblem(0, IProblem.PREPROCESSOR_INVALID_USE_OUTSIDE_PREPROCESSOR_DIRECTIVE, "__has_include"); + } + // #include public void testRelativeIncludes_243170() throws Exception { String content = getAboveComment(); diff --git a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF index 9e9d505c8e1..0fe0e42523d 100644 --- a/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF +++ b/core/org.eclipse.cdt.core/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.cdt.core; singleton:=true -Bundle-Version: 7.0.0.qualifier +Bundle-Version: 7.1.0.qualifier Bundle-Activator: org.eclipse.cdt.core.CCorePlugin Bundle-Vendor: %providerName Bundle-Localization: plugin diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/parser/GNUScannerExtensionConfiguration.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/parser/GNUScannerExtensionConfiguration.java index 212cf1e4c62..f168e395577 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/parser/GNUScannerExtensionConfiguration.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/dom/parser/GNUScannerExtensionConfiguration.java @@ -51,6 +51,8 @@ public abstract class GNUScannerExtensionConfiguration extends AbstractScannerEx addMacro("__builtin_va_arg(ap,type)", "*(typeof(type) *)ap"); addMacro("__builtin_types_compatible_p(x,y)", "__builtin_types_compatible_p(sizeof(x),sizeof(y))"); addMacro("__offsetof__(x)", "(x)"); + addMacro("__has_include", ""); + addMacro("__has_include_next", ""); addPreprocessorKeyword(Keywords.cINCLUDE_NEXT, IPreprocessorDirective.ppInclude_next); addPreprocessorKeyword(Keywords.cIMPORT, IPreprocessorDirective.ppImport); diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/IProblem.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/IProblem.java index 76c6accc4a0..4eecbf5c53b 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/IProblem.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/IProblem.java @@ -346,6 +346,11 @@ public interface IProblem { */ public final static int PREPROCESSOR_MULTIPLE_USER_DEFINED_SUFFIXES_IN_CONCATENATION = PREPROCESSOR_RELATED | 0x010; + /** + * @since 7.1 + */ + public final static int PREPROCESSOR_INVALID_USE_OUTSIDE_PREPROCESSOR_DIRECTIVE = PREPROCESSOR_RELATED | 0x011; + /* * Syntax error, detected by the parser. */ diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/Keywords.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/Keywords.java index beb29f190be..88b56e076db 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/Keywords.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/core/parser/Keywords.java @@ -314,6 +314,14 @@ public class Keywords { public static final char[] cDEFINED = "defined".toCharArray(); /** @since 5.11 */ public static final char[] c__HAS_FEATURE = "__has_feature".toCharArray(); + /** + * @since 7.1 + */ + public static final char[] c__HAS_INCLUDE = "__has_include".toCharArray(); + /** + * @since 7.1 + */ + public static final char[] c__HAS_INCLUDE_NEXT = "__has_include_next".toCharArray(); /** @since 5.2*/ public static final char[] _Pragma = "_Pragma".toCharArray(); public static final char[] cVA_ARGS = "__VA_ARGS__".toCharArray(); diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTProblem.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTProblem.java index 17518bba0e5..5746be46fc1 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTProblem.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/dom/parser/ASTProblem.java @@ -97,6 +97,8 @@ public class ASTProblem extends ASTNode implements IASTProblem { ParserMessages.getString("ScannerProblemFactory.error.scanner.floatWithBadPrefix")); //$NON-NLS-1$ errorMessages.put(Integer.valueOf(PREPROCESSOR_MULTIPLE_USER_DEFINED_SUFFIXES_IN_CONCATENATION), ParserMessages .getString("ScannerProblemFactory.error.preproc.multipleUserDefinedLiteralSuffixesOnStringLiteral")); //$NON-NLS-1$ + errorMessages.put(Integer.valueOf(PREPROCESSOR_INVALID_USE_OUTSIDE_PREPROCESSOR_DIRECTIVE), + ParserMessages.getString("ScannerProblemFactory.error.preproc.invalidUsageOutsidePreprocDirective")); //$NON-NLS-1$ errorMessages.put(Integer.valueOf(SYNTAX_ERROR), ParserMessages.getString("ParserProblemFactory.error.syntax.syntaxError")); //$NON-NLS-1$ errorMessages.put(Integer.valueOf(MISSING_SEMICOLON), diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/ParserMessages.properties b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/ParserMessages.properties index b867ea44e76..bc9e25aeab0 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/ParserMessages.properties +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/ParserMessages.properties @@ -30,6 +30,7 @@ ScannerProblemFactory.error.preproc.macroPasting=Invalid use of macro pasting in ScannerProblemFactory.error.preproc.missingRParen=missing '')'' in parameter list of macro: {0} ScannerProblemFactory.error.preproc.invalidVaArgs=__VA_ARGS__ can only appear in the expansion of a variadic macro\u0020 ScannerProblemFactory.error.preproc.multipleUserDefinedLiteralSuffixesOnStringLiteral=Multiple user-defined suffixes found when concatenating string literals +ScannerProblemFactory.error.preproc.invalidUsageOutsidePreprocDirective="{0}" can only appear in a preprocessor directive ScannerProblemFactory.error.scanner.invalidEscapeChar=Invalid escape character encountered\u0020 ScannerProblemFactory.error.scanner.unboundedString=Unbounded string encountered\u0020 diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/CPreprocessor.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/CPreprocessor.java index c6f17142c50..344d05c50f5 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/CPreprocessor.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/CPreprocessor.java @@ -92,6 +92,8 @@ public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { public static final int tNOSPACE = IToken.FIRST_RESERVED_PREPROCESSOR + 4; public static final int tMACRO_PARAMETER = IToken.FIRST_RESERVED_PREPROCESSOR + 5; public static final int t__HAS_FEATURE = IToken.FIRST_RESERVED_PREPROCESSOR + 6; + public static final int t__HAS_INCLUDE = IToken.FIRST_RESERVED_PREPROCESSOR + 7; + public static final int t__HAS_INCLUDE_NEXT = IToken.FIRST_RESERVED_PREPROCESSOR + 8; private static final int ORIGIN_PREPROCESSOR_DIRECTIVE = OffsetLimitReachedException.ORIGIN_PREPROCESSOR_DIRECTIVE; private static final int ORIGIN_INACTIVE_CODE = OffsetLimitReachedException.ORIGIN_INACTIVE_CODE; @@ -1323,6 +1325,11 @@ public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { } + InternalFileContent findInclusion(final String includeDirective, final boolean quoteInclude, + final boolean includeNext) { + return findInclusion(includeDirective, quoteInclude, includeNext, getCurrentFilename(), createCodeReaderTester); + } + private T findInclusion(final String includeDirective, final boolean quoteInclude, final boolean includeNext, final String currentFile, final IIncludeFileTester tester) { T reader = null; @@ -1808,7 +1815,7 @@ public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { fLocationMap.skippedFile(fLocationMap.getSequenceNumberForOffset(offset), fi); } - private char[] extractHeaderName(final char[] image, final char startDelim, final char endDelim, int[] offsets) { + char[] extractHeaderName(final char[] image, final char startDelim, final char endDelim, int[] offsets) { char[] headerName; int start = 0; int length = image.length; @@ -2124,9 +2131,6 @@ public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { * If applicable the macro is expanded and the resulting tokens are put onto a new context. * @param identifier the token where macro expansion may occur. * @param lexer the input for the expansion. - * @param stopAtNewline whether or not tokens to be read are limited to the current line. - * @param isPPCondition whether the expansion is inside of a preprocessor condition. This - * implies a specific handling for the defined token. */ private boolean expandMacro(final Token identifier, Lexer lexer, int options, boolean withinExpansion) throws OffsetLimitReachedException { @@ -2141,7 +2145,24 @@ public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { identifier.setType(t__HAS_FEATURE); return false; } + if (CharArrayUtils.equals(name, Keywords.c__HAS_INCLUDE)) { + identifier.setType(t__HAS_INCLUDE); + return false; + } + if (CharArrayUtils.equals(name, Keywords.c__HAS_INCLUDE_NEXT)) { + identifier.setType(t__HAS_INCLUDE_NEXT); + return false; + } } + + // These should not expand as macros and are not allowed outside #if, #ifdef + if (CharArrayUtils.equals(name, Keywords.c__HAS_INCLUDE) + || CharArrayUtils.equals(name, Keywords.c__HAS_INCLUDE_NEXT)) { + handleProblem(IProblem.PREPROCESSOR_INVALID_USE_OUTSIDE_PREPROCESSOR_DIRECTIVE, name, + identifier.getOffset(), identifier.getEndOffset()); + return false; + } + PreprocessorMacro macro = fMacroDictionary.get(name); if (macro == null) { if (reportSignificant && (options & IGNORE_UNDEFINED_SIGNIFICANT_MACROS) == 0) diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/ExpressionEvaluator.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/ExpressionEvaluator.java index 405010c737a..8e589416f21 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/ExpressionEvaluator.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/ExpressionEvaluator.java @@ -265,6 +265,10 @@ public class ExpressionEvaluator { return handleDefined(); case CPreprocessor.t__HAS_FEATURE: return handleHasFeature(); + case CPreprocessor.t__HAS_INCLUDE: + return handleHasInclude(false); + case CPreprocessor.t__HAS_INCLUDE_NEXT: + return handleHasInclude(true); case IToken.tLPAREN: consume(); long r1 = expression(); @@ -350,6 +354,51 @@ public class ExpressionEvaluator { return supported ? 1 : 0; } + private long handleHasInclude(boolean isIncludeNext) throws EvalException { + consume(); // '__has_include' + if (LA() != IToken.tLPAREN) { + throw new EvalException(IProblem.SCANNER_EXPRESSION_SYNTAX_ERROR, null); + } + consume(); // opening parenthesis + + // We don't have a tSYSTEM_HEADER_NAME or tQUOTED_HEADER_NAME token type here, these only get created when + // executing an include directive. In the case of a tSTRING, we only have to extract the header name from the + // quotes (extractHeaderName) but for system header, we have to reassemble the string by concatenating the + // tokens. + // A possibly more elegant solution could be to generate tSYSTEM_HEADER_NAME or tQUOTED_HEADER_NAME (during + // internalFetchToken?) but this is more ambitious and intrusive (more refactoring), especially considering + // various cases like macro expansion. + String headerName; + boolean quoteInclude = false; + if (LA() == IToken.tLT) { + headerName = ""; //$NON-NLS-1$ + while (fTokens.getType() != IToken.tGT) { + if (fTokens.getType() == IToken.tEND_OF_INPUT) { + throw new EvalException(IProblem.SCANNER_UNBOUNDED_STRING, null); + } + headerName += fTokens.getImage(); + consume(); + } + consume(); + headerName += ">"; //$NON-NLS-1$ + headerName = new String(fPreprocessor.extractHeaderName(headerName.toCharArray(), '<', '>', new int[2])); + } else if (LA() == IToken.tSTRING) { + quoteInclude = true; + headerName = new String(fPreprocessor.extractHeaderName(fTokens.getCharImage(), '"', '"', new int[2])); + consume(); + } else { + throw new EvalException(IProblem.SCANNER_EXPRESSION_SYNTAX_ERROR, null); + } + + if (LA() != IToken.tRPAREN) { + throw new EvalException(IProblem.SCANNER_MISSING_R_PAREN, null); + } + consume(); // closing parenthesis + + boolean includePathExists = fPreprocessor.findInclusion(headerName, quoteInclude, isIncludeNext) != null; + return includePathExists ? 1 : 0; + } + private int LA() { return fTokens.getType(); } diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/MacroExpander.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/MacroExpander.java index a292c8bd945..04a7e57bb2e 100644 --- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/MacroExpander.java +++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/parser/scanner/MacroExpander.java @@ -376,6 +376,12 @@ public class MacroExpander { t.setType(CPreprocessor.t__HAS_FEATURE); result.append(t); protect = true; + } else if (protectIntrinsics && Arrays.equals(image, Keywords.c__HAS_INCLUDE)) { + t.setType(CPreprocessor.t__HAS_INCLUDE); + result.append(t); + } else if (protectIntrinsics && Arrays.equals(image, Keywords.c__HAS_INCLUDE_NEXT)) { + t.setType(CPreprocessor.t__HAS_INCLUDE_NEXT); + result.append(t); } else if (macro == null || (macro.isFunctionStyle() && !input.findLParenthesis())) { // Tricky: Don't mark function-style macros if you don't find the left parenthesis if (fReportMacros != null) { diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTest_MacroRef_NoPrefix.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTest_MacroRef_NoPrefix.java index 9074534d853..3cbea62be57 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTest_MacroRef_NoPrefix.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTest_MacroRef_NoPrefix.java @@ -33,7 +33,8 @@ public class CompletionTest_MacroRef_NoPrefix extends CompletionProposalsBaseTes private final String[] expectedResults = { "AMacro(x)", "DEBUG", "XMacro(x, y)", "__CDT_PARSER__", "__COUNTER__", "__DATE__", "__FILE__", "__LINE__", "__STDC__", "__TIME__", "__builtin_va_arg(ap, type)", "__builtin_offsetof(T, m)", "__builtin_types_compatible_p(x, y)", "__complex__", "__cplusplus", - "__extension__", "__imag__", "__null", "__offsetof__(x)", "__real__", "__stdcall", "__thread", }; + "__extension__", "__has_include", "__has_include_next", "__imag__", "__null", "__offsetof__(x)", "__real__", + "__stdcall", "__thread", }; public CompletionTest_MacroRef_NoPrefix(String name) { super(name); diff --git a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests_PlainC.java b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests_PlainC.java index 9150aee571c..d058ddef885 100644 --- a/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests_PlainC.java +++ b/core/org.eclipse.cdt.ui.tests/ui/org/eclipse/cdt/ui/tests/text/contentassist2/CompletionTests_PlainC.java @@ -343,8 +343,8 @@ public class CompletionTests_PlainC extends AbstractContentAssistTest { final String[] expected = { "AMacro(x)", "DEBUG", "XMacro(x, y)", "__CDT_PARSER__", "__COUNTER__", "__DATE__", "__FILE__", "__LINE__", "__STDC_HOSTED__", "__STDC_VERSION__", "__STDC__", "__TIME__", "__builtin_va_arg(ap, type)", "__builtin_offsetof(T, m)", "__builtin_types_compatible_p(x, y)", - "__complex__", "__extension__", "__imag__", "__null", "__offsetof__(x)", "__real__", "__stdcall", - "__thread", }; + "__complex__", "__extension__", "__has_include", "__has_include_next", "__imag__", "__null", + "__offsetof__(x)", "__real__", "__stdcall", "__thread", }; assertCompletionResults(expected); }