| /******************************************************************************* |
| * Copyright (c) 2004, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| * Anton Leherbauer (Wind River Systems) |
| * Markus Schorn (Wind River Systems) |
| * Sergey Prigogin (Google) |
| * Marc-Andre Laperle (Ericsson) |
| * Richard Eames |
| *******************************************************************************/ |
| package org.eclipse.cdt.internal.core.parser.scanner; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.cdt.core.CCorePlugin; |
| import org.eclipse.cdt.core.dom.ast.IASTName; |
| import org.eclipse.cdt.core.dom.ast.IBinding; |
| import org.eclipse.cdt.core.dom.ast.IFileNomination; |
| import org.eclipse.cdt.core.dom.ast.IMacroBinding; |
| import org.eclipse.cdt.core.dom.parser.IScannerExtensionConfiguration; |
| import org.eclipse.cdt.core.index.IIndexMacro; |
| import org.eclipse.cdt.core.model.ITranslationUnit; |
| import org.eclipse.cdt.core.parser.AbstractParserLogService; |
| import org.eclipse.cdt.core.parser.EndOfFileException; |
| import org.eclipse.cdt.core.parser.ExtendedScannerInfo; |
| import org.eclipse.cdt.core.parser.FileContent; |
| import org.eclipse.cdt.core.parser.GCCKeywords; |
| import org.eclipse.cdt.core.parser.IExtendedScannerInfo; |
| import org.eclipse.cdt.core.parser.IMacro; |
| import org.eclipse.cdt.core.parser.IParserLogService; |
| import org.eclipse.cdt.core.parser.IPreprocessorDirective; |
| import org.eclipse.cdt.core.parser.IProblem; |
| import org.eclipse.cdt.core.parser.IScanner; |
| import org.eclipse.cdt.core.parser.IScannerInfo; |
| import org.eclipse.cdt.core.parser.ISignificantMacros; |
| import org.eclipse.cdt.core.parser.IToken; |
| import org.eclipse.cdt.core.parser.IncludeExportPatterns; |
| import org.eclipse.cdt.core.parser.IncludeFileContentProvider; |
| import org.eclipse.cdt.core.parser.Keywords; |
| import org.eclipse.cdt.core.parser.OffsetLimitReachedException; |
| import org.eclipse.cdt.core.parser.ParseError; |
| import org.eclipse.cdt.core.parser.ParserLanguage; |
| import org.eclipse.cdt.core.parser.util.CharArrayIntMap; |
| import org.eclipse.cdt.core.parser.util.CharArrayMap; |
| import org.eclipse.cdt.core.parser.util.CharArrayObjectMap; |
| import org.eclipse.cdt.core.parser.util.CharArraySet; |
| import org.eclipse.cdt.core.parser.util.CharArrayUtils; |
| import org.eclipse.cdt.internal.core.dom.IIncludeFileResolutionHeuristics; |
| import org.eclipse.cdt.internal.core.parser.EmptyFilesProvider; |
| import org.eclipse.cdt.internal.core.parser.IMacroDictionary; |
| import org.eclipse.cdt.internal.core.parser.scanner.ExpressionEvaluator.EvalException; |
| import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent.FileVersion; |
| import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContent.InclusionKind; |
| import org.eclipse.cdt.internal.core.parser.scanner.InternalFileContentProvider.DependsOnOutdatedFileException; |
| import org.eclipse.cdt.internal.core.parser.scanner.Lexer.LexerOptions; |
| import org.eclipse.cdt.internal.core.parser.scanner.MacroDefinitionParser.InvalidMacroDefinitionException; |
| import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.BranchKind; |
| import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.CodeState; |
| import org.eclipse.cdt.internal.core.parser.scanner.ScannerContext.Conditional; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.osgi.framework.Version; |
| |
| /** |
| * C-Preprocessor providing tokens for the parsers. The class should not be used directly, |
| * rather than that you should be using the {@link IScanner} interface. |
| * @since 5.0 |
| */ |
| public class CPreprocessor implements ILexerLog, IScanner, IAdaptable { |
| public static final String PROP_VALUE = "CPreprocessor"; //$NON-NLS-1$ |
| |
| public static final int tDEFINED = IToken.FIRST_RESERVED_PREPROCESSOR; |
| public static final int tEXPANDED_IDENTIFIER = IToken.FIRST_RESERVED_PREPROCESSOR + 1; |
| public static final int tSCOPE_MARKER = IToken.FIRST_RESERVED_PREPROCESSOR + 2; |
| public static final int tSPACE = IToken.FIRST_RESERVED_PREPROCESSOR + 3; |
| 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; |
| |
| private static final int ORIGIN_PREPROCESSOR_DIRECTIVE = OffsetLimitReachedException.ORIGIN_PREPROCESSOR_DIRECTIVE; |
| private static final int ORIGIN_INACTIVE_CODE = OffsetLimitReachedException.ORIGIN_INACTIVE_CODE; |
| |
| private static final char[] ONE = "1".toCharArray(); //$NON-NLS-1$ |
| |
| // Standard built-ins |
| private static final ObjectStyleMacro __CDT_PARSER__ = new ObjectStyleMacro("__CDT_PARSER__".toCharArray(), //$NON-NLS-1$ |
| Integer.toString(getCDTVersion()).toCharArray()); |
| private static final ObjectStyleMacro __cplusplus = new ObjectStyleMacro("__cplusplus".toCharArray(), //$NON-NLS-1$ |
| "201103L".toCharArray()); //$NON-NLS-1$ |
| private static final ObjectStyleMacro __STDC_HOSTED__ = new ObjectStyleMacro("__STDC_HOSTED__".toCharArray(), ONE); //$NON-NLS-1$ |
| private static final ObjectStyleMacro __STDC_VERSION__ = new ObjectStyleMacro("__STDC_VERSION__".toCharArray(), //$NON-NLS-1$ |
| "199901L".toCharArray()); //$NON-NLS-1$ |
| |
| private static final DynamicMacro __FILE__ = new FileMacro("__FILE__".toCharArray()); //$NON-NLS-1$ |
| private static final DynamicMacro __DATE__ = new DateMacro("__DATE__".toCharArray()); //$NON-NLS-1$ |
| private static final DynamicMacro __TIME__ = new TimeMacro("__TIME__".toCharArray()); //$NON-NLS-1$ |
| private static final DynamicMacro __LINE__ = new LineMacro("__LINE__".toCharArray()); //$NON-NLS-1$ |
| private static final char[] __COUNTER__ = "__COUNTER__".toCharArray(); //$NON-NLS-1$ |
| private static final char[] ONCE = "once".toCharArray(); //$NON-NLS-1$ |
| |
| static final int NO_EXPANSION = 0x01; |
| // Set in contexts where preprocessor intrinsics such as 'defined' |
| // or '__has_feature' need to be recognized. |
| static final int PROTECT_INTRINSICS = 0x02; |
| static final int STOP_AT_NL = 0x04; |
| static final int CHECK_NUMBERS = 0x08; |
| static final int REPORT_SIGNIFICANT_MACROS = 0x10; |
| static final int IGNORE_UNDEFINED_SIGNIFICANT_MACROS = 0x20; |
| |
| private static final int MAX_INCLUSION_DEPTH = 200; |
| |
| private static final String TRACE_NO_GUARD = CCorePlugin.PLUGIN_ID + "/debug/scanner/missingIncludeGuards"; //$NON-NLS-1$ |
| |
| /** |
| * Returns an integer, suitable for use as a macro value, representing the current |
| * version of the CDT feature, composited into a single number. |
| * For example, version 9.2.1 would be composited into 90201. |
| * Used as the value of the __CDT_PARSER__ macro. |
| */ |
| public static int getCDTVersion() { |
| Version version = CCorePlugin.getCDTFeatureVersion(); |
| if (version != null) { |
| int major = version.getMajor(); |
| int minor = version.getMinor(); |
| int micro = version.getMicro(); |
| int composite = major * 10000 + minor * 100 + micro; |
| return composite; |
| } |
| // Fall back to the old approach of defining __CDT_PARSER__ as 1. |
| return 1; |
| } |
| |
| private final class MacroDictionary implements IMacroDictionary, ISignificantMacros.IVisitor { |
| @Override |
| public boolean satisfies(ISignificantMacros significantMacros) { |
| return significantMacros.accept(this); |
| } |
| |
| @Override |
| public boolean visitDefined(char[] macro) { |
| return isDefined(macro); |
| } |
| |
| @Override |
| public boolean visitUndefined(char[] macro) { |
| return !isDefined(macro); |
| } |
| |
| @Override |
| public boolean visitValue(char[] macro, char[] value) { |
| PreprocessorMacro m = fMacroDictionary.get(macro); |
| return m != null && CharArrayUtils.equals(SignificantMacros.shortenValue(m.getExpansion()), value); |
| } |
| |
| private boolean isDefined(char[] macro) { |
| return fMacroDictionary.containsKey(macro); |
| } |
| } |
| |
| private interface IIncludeFileTester<T> { |
| T checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath); |
| } |
| |
| final private IIncludeFileTester<InternalFileContent> createCodeReaderTester = new IIncludeFileTester<InternalFileContent>() { |
| @Override |
| public InternalFileContent checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath) { |
| final InternalFileContent fc; |
| IFileNomination once = fFileContentProvider.isIncludedWithPragmaOnceSemantics(path); |
| if (once != null) { |
| fc = new InternalFileContent(path, InclusionKind.SKIP_FILE); |
| } else { |
| fc = fFileContentProvider.getContentForInclusion(path, fMacroDictionaryFacade); |
| } |
| if (fc != null) { |
| fc.setFoundByHeuristics(isHeuristicMatch); |
| fc.setFoundOnPath(onPath); |
| } |
| return fc; |
| } |
| }; |
| |
| private static class IncludeResolution { |
| final String fLocation; |
| final boolean fHeuristic; |
| |
| IncludeResolution(String location, boolean heusistic) { |
| fLocation = location; |
| fHeuristic = heusistic; |
| } |
| } |
| |
| final private IIncludeFileTester<IncludeResolution> createPathTester = new IIncludeFileTester<IncludeResolution>() { |
| @Override |
| public IncludeResolution checkFile(String path, boolean isHeuristicMatch, IncludeSearchPathElement onPath) { |
| if (fFileContentProvider.getInclusionExists(path)) { |
| return new IncludeResolution(path, isHeuristicMatch); |
| } |
| return null; |
| } |
| }; |
| |
| private final class TokenSequence implements ITokenSequence { |
| private final boolean fStopAtNewline; |
| |
| TokenSequence(boolean stopAtNewline) { |
| fStopAtNewline = stopAtNewline; |
| } |
| |
| @Override |
| public Token nextToken() throws OffsetLimitReachedException { |
| final Lexer lexer = fCurrentContext.getLexer(); |
| Token t = lexer.nextToken(); |
| if (t.getType() == IToken.tPOUND && lexer.currentTokenIsFirstOnLine()) { |
| executeDirective(lexer, t.getOffset(), true); |
| t = lexer.currentToken(); |
| } |
| if (fStopAtNewline && t.getType() == Lexer.tNEWLINE) |
| return new Token(IToken.tEND_OF_INPUT, null, 0, 0); |
| |
| return t; |
| } |
| |
| @Override |
| public int getLastEndOffset() { |
| return fCurrentContext.getLexer().getLastEndOffset(); |
| } |
| |
| @Override |
| public Token currentToken() { |
| Token t = fCurrentContext.currentLexerToken(); |
| if (fStopAtNewline && t.getType() == Lexer.tNEWLINE) |
| return new Token(IToken.tEND_OF_INPUT, null, 0, 0); |
| |
| return t; |
| } |
| } |
| |
| private Set<String> sSupportedFeatures; |
| |
| TokenSequence fInputToMacroExpansion = new TokenSequence(false); |
| TokenSequence fLineInputToMacroExpansion = new TokenSequence(true); |
| |
| final private AbstractParserLogService fLog; |
| final private InternalFileContentProvider fFileContentProvider; |
| |
| private final IIncludeFileResolutionHeuristics fIncludeFileResolutionHeuristics; |
| private final ExpressionEvaluator fExpressionEvaluator; |
| private final MacroDefinitionParser fMacroDefinitionParser; |
| private final MacroExpander fMacroExpander; |
| |
| // configuration |
| final private LexerOptions fLexOptions = new LexerOptions(); |
| final private char[] fAdditionalNumericLiteralSuffixes; |
| final private CharArrayIntMap fKeywords; |
| final private CharArrayIntMap fPPKeywords; |
| private final IncludeSearchPath fIncludeSearchPath; |
| private String[][] fPreIncludedFiles = null; |
| |
| private int fContentAssistLimit = -1; |
| private boolean fHandledCompletion = false; |
| private boolean fSplitShiftRightOperator = false; |
| |
| // State information |
| private final CharArrayMap<PreprocessorMacro> fMacroDictionary = new CharArrayMap<>(512); |
| private final IMacroDictionary fMacroDictionaryFacade = new MacroDictionary(); |
| private final LocationMap fLocationMap; |
| private CharArraySet fPreventInclusion; |
| private CharArraySet fImports; |
| |
| private final ScannerContext fRootContext; |
| protected ScannerContext fCurrentContext; |
| |
| private boolean isCancelled; |
| private boolean fIsFirstFetchToken = true; |
| |
| private Token fPrefetchedTokens; |
| private Token fLastToken; |
| |
| private InternalFileContent fRootContent; |
| private boolean fHandledEndOfTranslationUnit; |
| |
| // Detection of include guards used around an include directive |
| private char[] fExternIncludeGuard; |
| private Set<String> fTracedGuards; |
| |
| public CPreprocessor(FileContent fileContent, IScannerInfo info, ParserLanguage language, IParserLogService log, |
| IScannerExtensionConfiguration configuration, IncludeFileContentProvider readerFactory) { |
| Token.resetCounterFor(info); |
| if (readerFactory instanceof InternalFileContentProvider) { |
| fFileContentProvider = (InternalFileContentProvider) readerFactory; |
| } else if (readerFactory == null) { |
| fFileContentProvider = EmptyFilesProvider.getInstance(); |
| } else { |
| throw new IllegalArgumentException("Illegal reader factory"); //$NON-NLS-1$ |
| } |
| if (fileContent instanceof InternalFileContent) { |
| fRootContent = (InternalFileContent) fileContent; |
| } else { |
| throw new IllegalArgumentException("Illegal file content object"); //$NON-NLS-1$ |
| } |
| |
| fLog = AbstractParserLogService.convert(log); |
| fAdditionalNumericLiteralSuffixes = nonNull(configuration.supportAdditionalNumericLiteralSuffixes()); |
| fLexOptions.fSupportDollarInIdentifiers = configuration.support$InIdentifiers(); |
| fLexOptions.fSupportAtSignInIdentifiers = configuration.supportAtSignInIdentifiers(); |
| fLexOptions.fSupportMinAndMax = configuration.supportMinAndMaxOperators(); |
| fLexOptions.fSupportSlashPercentComments = configuration.supportSlashPercentComments(); |
| fLexOptions.fSupportUTFLiterals = configuration.supportUTFLiterals(); |
| fLexOptions.fSupportRawStringLiterals = configuration.supportRawStringLiterals(); |
| fLexOptions.fSupportUserDefinedLiterals = configuration.supportUserDefinedLiterals(); |
| fLexOptions.fSupportDigitSeparators = configuration.supportDigitSeparators(); |
| if (info instanceof ExtendedScannerInfo) |
| fLexOptions.fIncludeExportPatterns = ((ExtendedScannerInfo) info).getIncludeExportPatterns(); |
| fLocationMap = new LocationMap(fLexOptions); |
| fKeywords = new CharArrayIntMap(40, -1); |
| fPPKeywords = new CharArrayIntMap(40, -1); |
| configureKeywords(language, configuration); |
| |
| fExpressionEvaluator = new ExpressionEvaluator(this); |
| fMacroDefinitionParser = new MacroDefinitionParser(); |
| fMacroExpander = new MacroExpander(this, fMacroDictionary, fLocationMap, fLexOptions); |
| fIncludeFileResolutionHeuristics = fFileContentProvider.getIncludeHeuristics(); |
| |
| String contextPath = fFileContentProvider.getContextPath(); |
| if (contextPath == null) { |
| contextPath = fRootContent.getFileLocation(); |
| } |
| fIncludeSearchPath = configureIncludeSearchPath(new File(contextPath).getParentFile(), info); |
| setupMacroDictionary(configuration, info, language); |
| |
| ILocationCtx ctx = fLocationMap.pushTranslationUnit(fRootContent.getFileLocation(), fRootContent.getSource()); |
| Lexer lexer = new Lexer(fRootContent.getSource(), fLexOptions, this, this); |
| fRootContext = fCurrentContext = new ScannerContext(ctx, null, lexer); |
| if (info instanceof IExtendedScannerInfo) { |
| final IExtendedScannerInfo einfo = (IExtendedScannerInfo) info; |
| fPreIncludedFiles = new String[][] { einfo.getMacroFiles(), einfo.getIncludeFiles() }; |
| } |
| fFileContentProvider.resetForTranslationUnit(); |
| } |
| |
| private char[] detectIncludeGuard(String filePath, AbstractCharArray source, ScannerContext ctx) { |
| if (!fFileContentProvider.shouldIndexAllHeaderVersions(filePath)) { |
| final char[] guard = IncludeGuardDetection.detectIncludeGuard(source, fLexOptions, fPPKeywords); |
| if (guard != null) { |
| IFileNomination nom = fLocationMap.reportPragmaOnceSemantics(ctx.getLocationCtx()); |
| fFileContentProvider.reportPragmaOnceSemantics(filePath, nom); |
| ctx.internalModification(guard); |
| ctx.setPragmaOnce(true); |
| return guard; |
| } |
| } |
| |
| ctx.trackSignificantMacros(); |
| |
| if (ctx != fRootContext) { |
| if (fLog.isTracing(TRACE_NO_GUARD)) { |
| if (fTracedGuards == null) |
| fTracedGuards = new HashSet<>(); |
| if (fTracedGuards.add(filePath)) |
| fLog.traceLog(TRACE_NO_GUARD, "No include guard in " + filePath); //$NON-NLS-1$ |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public void setSplitShiftROperator(boolean val) { |
| fSplitShiftRightOperator = val; |
| } |
| |
| @Override |
| public void setComputeImageLocations(boolean val) { |
| fLexOptions.fCreateImageLocations = val; |
| } |
| |
| @Override |
| public void setTrackIncludeExport(IncludeExportPatterns patterns) { |
| fLexOptions.fIncludeExportPatterns = patterns; |
| } |
| |
| @Override |
| public void setContentAssistMode(int offset) { |
| fContentAssistLimit = offset; |
| fRootContext.getLexer().setContentAssistMode(offset); |
| } |
| |
| public boolean isContentAssistMode() { |
| return fRootContext.getLexer().isContentAssistMode(); |
| } |
| |
| @Override |
| public void setProcessInactiveCode(boolean val) { |
| fRootContext.setParseInactiveCode(val); |
| } |
| |
| public ITranslationUnit getTranslationUnit() { |
| return fRootContent.getTranslationUnit(); |
| } |
| |
| public boolean isSource() { |
| return fRootContent.isSource(); |
| } |
| |
| @Override |
| public ILocationResolver getLocationResolver() { |
| return fLocationMap; |
| } |
| |
| /** |
| * @see IScannerExtensionConfiguration#supportAdditionalNumericLiteralSuffixes |
| * @since 5.10 |
| */ |
| @Override |
| public char[] getAdditionalNumericLiteralSuffixes() { |
| return fAdditionalNumericLiteralSuffixes; |
| } |
| |
| private void configureKeywords(ParserLanguage language, IScannerExtensionConfiguration configuration) { |
| Keywords.addKeywordsPreprocessor(fPPKeywords); |
| if (language == ParserLanguage.C) { |
| Keywords.addKeywordsC(fKeywords); |
| } else { |
| Keywords.addKeywordsCpp(fKeywords); |
| } |
| CharArrayIntMap additionalKeywords = configuration.getAdditionalKeywords(); |
| if (additionalKeywords != null) { |
| fKeywords.putAll(additionalKeywords); |
| } |
| additionalKeywords = configuration.getAdditionalPreprocessorKeywords(); |
| if (additionalKeywords != null) { |
| fPPKeywords.putAll(additionalKeywords); |
| } |
| } |
| |
| protected String getCurrentFilename() { |
| return fLocationMap.getCurrentFilePath(); |
| } |
| |
| private char[] nonNull(char[] array) { |
| return array == null ? CharArrayUtils.EMPTY_CHAR_ARRAY : array; |
| } |
| |
| /** |
| * Returns include search path for a given current directory and a IScannerInfo. |
| * @param directory the current directory |
| * @param info scanner information, or {@code null} if not available |
| * @return the include search path |
| */ |
| public static IncludeSearchPath configureIncludeSearchPath(File directory, IScannerInfo info) { |
| boolean inhibitUseOfCurrentFileDirectory = false; |
| List<IncludeSearchPathElement> elements = new ArrayList<>(); |
| |
| if (info != null) { |
| // Quote includes first |
| if (info instanceof IExtendedScannerInfo) { |
| final IExtendedScannerInfo einfo = (IExtendedScannerInfo) info; |
| final String[] paths = einfo.getLocalIncludePath(); |
| if (paths != null) { |
| for (String path : paths) { |
| if ("-".equals(path)) { //$NON-NLS-1$ |
| inhibitUseOfCurrentFileDirectory = true; |
| } else { |
| elements.add(new IncludeSearchPathElement(makeAbsolute(directory, path), true)); |
| } |
| } |
| } |
| } |
| // Regular includes |
| String[] paths = info.getIncludePaths(); |
| if (paths != null) { |
| for (String path : paths) { |
| if ("-".equals(path)) { //$NON-NLS-1$ |
| inhibitUseOfCurrentFileDirectory = true; |
| } else { |
| elements.add(new IncludeSearchPathElement(makeAbsolute(directory, path), false)); |
| } |
| } |
| } |
| } |
| return new IncludeSearchPath(elements, inhibitUseOfCurrentFileDirectory); |
| } |
| |
| private static String makeAbsolute(File directory, String includePath) { |
| if (directory == null || new File(includePath).isAbsolute()) { |
| return includePath; |
| } |
| return ScannerUtility.createReconciledPath(directory.getAbsolutePath(), includePath); |
| } |
| |
| private void setupMacroDictionary(IScannerExtensionConfiguration config, IScannerInfo info, ParserLanguage lang) { |
| // Built-in macros |
| fMacroDictionary.put(__CDT_PARSER__.getNameCharArray(), __CDT_PARSER__); |
| fMacroDictionary.put(__FILE__.getNameCharArray(), __FILE__); |
| fMacroDictionary.put(__DATE__.getNameCharArray(), __DATE__); |
| fMacroDictionary.put(__TIME__.getNameCharArray(), __TIME__); |
| fMacroDictionary.put(__LINE__.getNameCharArray(), __LINE__); |
| fMacroDictionary.put(__COUNTER__, new CounterMacro(__COUNTER__)); |
| |
| if (lang == ParserLanguage.CPP) { |
| fMacroDictionary.put(__cplusplus.getNameCharArray(), __cplusplus); |
| } else { |
| fMacroDictionary.put(__STDC_HOSTED__.getNameCharArray(), __STDC_HOSTED__); |
| fMacroDictionary.put(__STDC_VERSION__.getNameCharArray(), __STDC_VERSION__); |
| } |
| |
| IMacro[] toAdd = config.getAdditionalMacros(); |
| if (toAdd != null) { |
| for (final IMacro macro : toAdd) { |
| addMacroDefinition(macro.getSignature(), macro.getExpansion()); |
| } |
| } |
| |
| final Map<String, String> macroDict = info.getDefinedSymbols(); |
| if (macroDict != null) { |
| for (Map.Entry<String, String> entry : macroDict.entrySet()) { |
| final String key = entry.getKey(); |
| final String value = entry.getValue().trim(); |
| addMacroDefinition(key.toCharArray(), value.toCharArray()); |
| } |
| } |
| |
| Collection<PreprocessorMacro> predefined = fMacroDictionary.values(); |
| for (PreprocessorMacro macro : predefined) { |
| fLocationMap.registerPredefinedMacro(macro); |
| } |
| } |
| |
| private void beforeFirstFetchToken() { |
| if (fPreIncludedFiles != null) { |
| handlePreIncludedFiles(); |
| fPreIncludedFiles = null; |
| } |
| final String location = fLocationMap.getTranslationUnitPath(); |
| InternalFileContent content; |
| try { |
| content = fFileContentProvider.getContentForContextToHeaderGap(location, fMacroDictionaryFacade); |
| } catch (DependsOnOutdatedFileException e) { |
| // Abort the parser, handled by the abstract indexer task. |
| throw new RuntimeException(e); |
| } |
| if (content != null && content.getKind() == InclusionKind.FOUND_IN_INDEX) { |
| processInclusionFromIndex(0, content, false); |
| } |
| |
| detectIncludeGuard(location, fRootContent.getSource(), fRootContext); |
| fLocationMap.parsingFile(fFileContentProvider, fRootContent); |
| fRootContent = null; |
| } |
| |
| private void handlePreIncludedFiles() { |
| final String[] imacro = fPreIncludedFiles[0]; |
| if (imacro != null && imacro.length > 0) { |
| final char[] buffer = createSyntheticFile(imacro); |
| ILocationCtx ctx = fLocationMap.pushPreInclusion(new CharArray(buffer), 0, true); |
| fCurrentContext = new ScannerContext(ctx, fCurrentContext, new Lexer(buffer, fLexOptions, this, this)); |
| ScannerContext preCtx = fCurrentContext; |
| try { |
| while (internalFetchToken(preCtx, CHECK_NUMBERS, false).getType() != IToken.tEND_OF_INPUT) { |
| // just eat the tokens |
| } |
| final ILocationCtx locationCtx = fCurrentContext.getLocationCtx(); |
| fLocationMap.popContext(locationCtx); |
| fCurrentContext = fCurrentContext.getParent(); |
| assert fCurrentContext == fRootContext; |
| } catch (OffsetLimitReachedException e) { |
| } |
| } |
| final String[] include = fPreIncludedFiles[1]; |
| if (include != null && include.length > 0) { |
| final char[] buffer = createSyntheticFile(include); |
| ILocationCtx ctx = fLocationMap.pushPreInclusion(new CharArray(buffer), 0, false); |
| fCurrentContext = new ScannerContext(ctx, fCurrentContext, new Lexer(buffer, fLexOptions, this, this)); |
| } |
| fPreIncludedFiles = null; |
| } |
| |
| private char[] createSyntheticFile(String[] files) { |
| int totalLength = 0; |
| final char[] instruction = "#include <".toCharArray(); //$NON-NLS-1$ |
| for (String file : files) { |
| totalLength += instruction.length + 2 + file.length(); |
| } |
| final char[] buffer = new char[totalLength]; |
| int pos = 0; |
| for (String file : files) { |
| final char[] fileName = file.toCharArray(); |
| System.arraycopy(instruction, 0, buffer, pos, instruction.length); |
| pos += instruction.length; |
| System.arraycopy(fileName, 0, buffer, pos, fileName.length); |
| pos += fileName.length; |
| buffer[pos++] = '>'; |
| buffer[pos++] = '\n'; |
| } |
| return buffer; |
| } |
| |
| public PreprocessorMacro addMacroDefinition(char[] key, char[] value) { |
| final Lexer lex = new Lexer(key, fLexOptions, ILexerLog.NULL, null); |
| try { |
| PreprocessorMacro result = fMacroDefinitionParser.parseMacroDefinition(lex, ILexerLog.NULL, value); |
| fLocationMap.registerPredefinedMacro(result); |
| fMacroDictionary.put(result.getNameCharArray(), result); |
| return result; |
| } catch (Exception e) { |
| fLog.traceLog("Invalid macro definition: '" + String.valueOf(key) + "'"); //$NON-NLS-1$//$NON-NLS-2$ |
| return null; |
| } |
| } |
| |
| @Override |
| public Map<String, IMacroBinding> getMacroDefinitions() { |
| Map<String, IMacroBinding> hashMap = new HashMap<>(fMacroDictionary.size()); |
| for (char[] key : fMacroDictionary.keys()) { |
| hashMap.put(String.valueOf(key), fMacroDictionary.get(key)); |
| } |
| return hashMap; |
| } |
| |
| @Override |
| public boolean isOnTopContext() { |
| ScannerContext ctx = fCurrentContext; |
| while (ctx != null && ctx.getLocationCtx() instanceof LocationCtxMacroExpansion) { |
| ctx = ctx.getParent(); |
| } |
| return ctx == fRootContext; |
| } |
| |
| @Override |
| public void cancel() { |
| isCancelled = true; |
| } |
| |
| /** |
| * Returns the next token from the preprocessor without concatenating string literals |
| * and also without splitting the shift-right operator. |
| */ |
| private Token fetchToken() throws OffsetLimitReachedException { |
| if (fIsFirstFetchToken) { |
| beforeFirstFetchToken(); |
| fIsFirstFetchToken = false; |
| } |
| Token t = fPrefetchedTokens; |
| if (t != null) { |
| fPrefetchedTokens = (Token) t.getNext(); |
| t.setNext(null); |
| return t; |
| } |
| try { |
| t = internalFetchToken(fRootContext, |
| CHECK_NUMBERS | REPORT_SIGNIFICANT_MACROS | IGNORE_UNDEFINED_SIGNIFICANT_MACROS, false); |
| } catch (OffsetLimitReachedException e) { |
| fHandledCompletion = true; |
| throw e; |
| } |
| final int offset = fLocationMap.getSequenceNumberForOffset(t.getOffset()); |
| final int endOffset = fLocationMap.getSequenceNumberForOffset(t.getEndOffset()); |
| t.setOffset(offset, endOffset); |
| t.setNext(null); |
| return t; |
| } |
| |
| private void pushbackToken(Token t) { |
| t.setNext(fPrefetchedTokens); |
| fPrefetchedTokens = t; |
| } |
| |
| /** |
| * Returns next token for the parser. String literals are not concatenated. When |
| * the end is reached tokens with type {@link IToken#tEND_OF_INPUT}. |
| * @throws OffsetLimitReachedException see {@link Lexer}. |
| */ |
| public IToken nextTokenRaw() throws OffsetLimitReachedException { |
| if (isCancelled) { |
| throw new ParseError(ParseError.ParseErrorKind.TIMEOUT_OR_CANCELLED); |
| } |
| |
| Token t1 = fetchToken(); |
| switch (t1.getType()) { |
| case IToken.tCOMPLETION: |
| fHandledCompletion = true; |
| break; |
| case IToken.tEND_OF_INPUT: |
| if (fContentAssistLimit >= 0) { |
| int useType = fHandledCompletion ? IToken.tEOC : IToken.tCOMPLETION; |
| int sequenceNumber = fLocationMap.getSequenceNumberForOffset(fContentAssistLimit); |
| t1 = new Token(useType, null, sequenceNumber, sequenceNumber); |
| fHandledCompletion = true; |
| } |
| break; |
| case IToken.t_PRAGMA: |
| handlePragmaOperator(t1); |
| return nextTokenRaw(); |
| } |
| if (fLastToken != null) { |
| fLastToken.setNext(t1); |
| } |
| fLastToken = t1; |
| return t1; |
| } |
| |
| private void handlePragmaOperator(Token t1) throws OffsetLimitReachedException { |
| Token t2 = fetchToken(); |
| int end; |
| if (t2.getType() == IToken.tLPAREN) { |
| Token t3 = fetchToken(); |
| end = t3.getEndOffset(); |
| final int tt = t3.getType(); |
| if (tt == IToken.tSTRING || tt == IToken.tLSTRING || tt == IToken.tUTF16STRING |
| || tt == IToken.tUTF32STRING) { |
| Token t4 = fetchToken(); |
| end = t4.getEndOffset(); |
| if (t4.getType() == IToken.tRPAREN) { |
| fLocationMap.encounterPragmaOperator(t1.getOffset(), t3.getOffset(), t3.getEndOffset(), |
| t4.getEndOffset()); |
| return; |
| } else { |
| end = t3.getEndOffset(); |
| pushbackToken(t4); |
| } |
| } else { |
| if (t3.getType() == IToken.tRPAREN) { |
| // Consume closing parenthesis |
| end = t3.getEndOffset(); |
| } else { |
| end = t2.getEndOffset(); |
| pushbackToken(t3); |
| } |
| } |
| } else { |
| end = t1.getEndOffset(); |
| pushbackToken(t2); |
| } |
| fLocationMap.encounterProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, t1.getCharImage(), t1.getOffset(), end); |
| } |
| |
| /** |
| * Returns next token for the parser. String literals are concatenated. |
| * @throws EndOfFileException when the end of the translation unit has been reached. |
| * @throws OffsetLimitReachedException see {@link Lexer}. |
| */ |
| @Override |
| public IToken nextToken() throws EndOfFileException { |
| if (isCancelled) { |
| throw new ParseError(ParseError.ParseErrorKind.TIMEOUT_OR_CANCELLED); |
| } |
| |
| Token t1 = fetchToken(); |
| char[] udlSuffix = null; |
| |
| final int tt1 = t1.getType(); |
| switch (tt1) { |
| case IToken.tCOMPLETION: |
| fHandledCompletion = true; |
| break; |
| |
| case IToken.tEND_OF_INPUT: |
| if (fContentAssistLimit < 0) { |
| fPrefetchedTokens = t1; |
| throw new EndOfFileException(t1.getOffset()); |
| } |
| int useType = fHandledCompletion ? IToken.tEOC : IToken.tCOMPLETION; |
| int sequenceNumber = fLocationMap.getSequenceNumberForOffset(fContentAssistLimit); |
| t1 = new Token(useType, null, sequenceNumber, sequenceNumber); |
| fHandledCompletion = true; |
| break; |
| |
| case IToken.t_PRAGMA: |
| handlePragmaOperator(t1); |
| return nextToken(); |
| case IToken.tUSER_DEFINED_STRING_LITERAL: |
| udlSuffix = getUserDefinedLiteralSuffix(t1); |
| //$FALL-THROUGH$ |
| case IToken.tSTRING: |
| case IToken.tLSTRING: |
| case IToken.tUTF16STRING: |
| case IToken.tUTF32STRING: |
| StringType st = StringType.fromToken(t1); |
| Token t2; |
| StringBuilder buf = null; |
| int endOffset = t1.getEndOffset(); |
| loop: while (true) { |
| t2 = fetchToken(); |
| final int tt2 = t2.getType(); |
| switch (tt2) { |
| case IToken.tUSER_DEFINED_STRING_LITERAL: |
| if (udlSuffix == null) { |
| udlSuffix = getUserDefinedLiteralSuffix(t2); |
| } else if (!Arrays.equals(udlSuffix, getUserDefinedLiteralSuffix(t2))) { |
| handleProblem(IProblem.PREPROCESSOR_MULTIPLE_USER_DEFINED_SUFFIXES_IN_CONCATENATION, udlSuffix, |
| t2.getOffset(), endOffset); |
| } |
| //$FALL-THROUGH$ |
| case IToken.tLSTRING: |
| case IToken.tSTRING: |
| case IToken.tUTF16STRING: |
| case IToken.tUTF32STRING: |
| st = StringType.max(st, StringType.fromToken(t2)); |
| if (buf == null) { |
| buf = new StringBuilder(); |
| appendStringContent(buf, t1); |
| } |
| appendStringContent(buf, t2); |
| endOffset = t2.getEndOffset(); |
| continue loop; |
| case IToken.tINACTIVE_CODE_START: |
| // no support for inactive code after a string literal |
| skipInactiveCode(); |
| continue loop; |
| case IToken.t_PRAGMA: |
| handlePragmaOperator(t2); |
| continue loop; |
| default: |
| break loop; |
| } |
| } |
| pushbackToken(t2); |
| if (buf != null) { |
| char[] prefix = st.getPrefix(); |
| final int imageLength = buf.length() + prefix.length + 2 + (udlSuffix == null ? 0 : udlSuffix.length); |
| char[] image = new char[imageLength]; |
| int off = -1; |
| int tokenType = st.getTokenValue(); |
| |
| for (char c : prefix) |
| image[++off] = c; |
| |
| image[++off] = '"'; |
| buf.getChars(0, buf.length(), image, ++off); |
| off += buf.length(); |
| image[off] = '"'; |
| if (udlSuffix != null) { |
| System.arraycopy(udlSuffix, 0, image, ++off, udlSuffix.length); |
| tokenType = IToken.tUSER_DEFINED_STRING_LITERAL; |
| } |
| t1 = new TokenWithImage(tokenType, null, t1.getOffset(), endOffset, image); |
| } |
| break; |
| |
| case IToken.tSHIFTR: |
| if (fSplitShiftRightOperator) { |
| int offset = t1.getOffset(); |
| endOffset = t1.getEndOffset(); |
| |
| t1.setType(IToken.tGT_in_SHIFTR); |
| t1.setOffset(offset, offset + 1); |
| t2 = new Token(IToken.tGT_in_SHIFTR, t1.fSource, offset + 1, endOffset); |
| pushbackToken(t2); |
| } |
| } |
| |
| if (fLastToken != null) { |
| fLastToken.setNext(t1); |
| } |
| fLastToken = t1; |
| return t1; |
| } |
| |
| @Override |
| public void skipInactiveCode() throws OffsetLimitReachedException { |
| final Lexer lexer = fCurrentContext.getLexer(); |
| if (lexer != null) { |
| CodeState state = fCurrentContext.getCodeState(); |
| while (state != CodeState.eActive) { |
| state = skipBranch(lexer, false); |
| } |
| fCurrentContext.clearInactiveCodeMarkerToken(); |
| } |
| } |
| |
| @Override |
| public int getCodeBranchNesting() { |
| return fCurrentContext.getCodeBranchNesting(); |
| } |
| |
| private void appendStringContent(StringBuilder buf, Token t1) { |
| final char[] image = t1.getCharImage(); |
| final int length = image.length; |
| int start = 1; |
| for (char c : image) { |
| if (c == '"') |
| break; |
| start++; |
| } |
| |
| if (length > 1) { |
| int diff = 0; |
| if (t1.getType() == IToken.tUSER_DEFINED_STRING_LITERAL) { |
| diff = t1.getImage().lastIndexOf('"') - start; |
| } else { |
| diff = image[length - 1] == '"' ? length - start - 1 : length - start; |
| } |
| if (diff > 0) { |
| buf.append(image, start, diff); |
| } |
| } |
| } |
| |
| Token internalFetchToken(final ScannerContext uptoEndOfCtx, int options, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| |
| Token ppToken = fCurrentContext.currentLexerToken(); |
| while (true) { |
| switch (ppToken.getType()) { |
| case Lexer.tBEFORE_INPUT: |
| ppToken = fCurrentContext.nextPPToken(); |
| continue; |
| |
| case Lexer.tNEWLINE: |
| if ((options & STOP_AT_NL) != 0) { |
| return ppToken; |
| } |
| ppToken = fCurrentContext.nextPPToken(); |
| continue; |
| |
| case Lexer.tOTHER_CHARACTER: |
| handleProblem(IProblem.SCANNER_BAD_CHARACTER, ppToken.getCharImage(), ppToken.getOffset(), |
| ppToken.getEndOffset()); |
| ppToken = fCurrentContext.nextPPToken(); |
| continue; |
| |
| case IToken.tEND_OF_INPUT: |
| if (fCurrentContext == uptoEndOfCtx || uptoEndOfCtx == null) { |
| if (fCurrentContext == fRootContext && !fHandledEndOfTranslationUnit |
| && (options & STOP_AT_NL) == 0) { |
| fHandledEndOfTranslationUnit = true; |
| fLocationMap.endTranslationUnit(ppToken.getEndOffset(), fCurrentContext.getSignificantMacros()); |
| } |
| return ppToken; |
| } |
| |
| final ILocationCtx locationCtx = fCurrentContext.getLocationCtx(); |
| ASTInclusionStatement inc = locationCtx.getInclusionStatement(); |
| if (inc != null) { |
| completeInclusion(inc); |
| } |
| fLocationMap.popContext(locationCtx); |
| |
| fCurrentContext.propagateSignificantMacros(); |
| fCurrentContext = fCurrentContext.getParent(); |
| assert fCurrentContext != null; |
| |
| ppToken = fCurrentContext.currentLexerToken(); |
| continue; |
| |
| case IToken.tPOUND: { |
| final Lexer lexer = fCurrentContext.getLexer(); |
| if (lexer != null && lexer.currentTokenIsFirstOnLine()) { |
| executeDirective(lexer, ppToken.getOffset(), withinExpansion); |
| ppToken = fCurrentContext.currentLexerToken(); |
| continue; |
| } |
| break; |
| } |
| |
| case IToken.tIDENTIFIER: |
| fCurrentContext.nextPPToken(); // consume the identifier |
| if ((options & NO_EXPANSION) == 0) { |
| final Lexer lexer = fCurrentContext.getLexer(); |
| if (lexer != null && expandMacro(ppToken, lexer, options, withinExpansion)) { |
| ppToken = fCurrentContext.currentLexerToken(); |
| continue; |
| } |
| |
| final char[] name = ppToken.getCharImage(); |
| int tokenType = fKeywords.get(name); |
| if (tokenType != fKeywords.undefined) { |
| ppToken.setType(tokenType); |
| } |
| } |
| return ppToken; |
| |
| case IToken.tINTEGER: |
| if ((options & CHECK_NUMBERS) != 0) { |
| checkNumber(ppToken, false); |
| } |
| break; |
| |
| case IToken.tFLOATINGPT: |
| if ((options & CHECK_NUMBERS) != 0) { |
| checkNumber(ppToken, true); |
| } |
| break; |
| } |
| fCurrentContext.nextPPToken(); |
| return ppToken; |
| } |
| } |
| |
| private void completeInclusion(ASTInclusionStatement inc) { |
| final ISignificantMacros sig; |
| CharArrayObjectMap<char[]> sigMacros = fCurrentContext.getSignificantMacros(); |
| if (sigMacros == null || sigMacros.isEmpty()) { |
| sig = ISignificantMacros.NONE; |
| } else { |
| sig = new SignificantMacros(sigMacros); |
| } |
| inc.setSignificantMacros(sig); |
| if (!inc.hasPragmaOnceSemantics()) { |
| fFileContentProvider.addLoadedVersions(inc.getPath(), fCurrentContext.getLoadedVersionCount(), sig); |
| } |
| } |
| |
| private void checkNumber(Token number, final boolean isFloat) { |
| final char[] image = number.getCharImage(); |
| boolean hasExponent = false; |
| |
| // Integer constants written in binary are a non-standard extension |
| // supported by GCC since 4.3 and by some other C compilers |
| // They consist of a prefix 0b or 0B, followed by a sequence of 0 and 1 digits |
| // see http://gcc.gnu.org/onlinedocs/gcc/Binary-constants.html |
| boolean isBin = false; |
| |
| boolean isHex = false; |
| boolean isOctal = false; |
| boolean hasDot = false; |
| |
| boolean badSuffix = false; |
| |
| int pos = 0; |
| if (image.length > 1) { |
| if (image[0] == '0') { |
| switch (image[++pos]) { |
| case 'b': |
| case 'B': |
| isBin = true; |
| ++pos; |
| break; |
| case 'x': |
| case 'X': |
| isHex = true; |
| ++pos; |
| break; |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| if (!isFloat) |
| isOctal = true; |
| ++pos; |
| break; |
| case '8': |
| case '9': |
| if (!isFloat) { |
| handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(), |
| number.getEndOffset()); |
| return; |
| } |
| ++pos; |
| break; |
| } |
| } |
| } |
| |
| loop: for (; pos < image.length; pos++) { |
| if (isBin) { |
| switch (image[pos]) { |
| case '0': |
| case '1': |
| continue; |
| case '\'': |
| if (fLexOptions.fSupportDigitSeparators) { |
| continue; |
| } else { |
| break loop; |
| } |
| case 'e': |
| case 'E': |
| case '.': |
| handleProblem(IProblem.SCANNER_FLOAT_WITH_BAD_PREFIX, "0b".toCharArray(), number.getOffset(), //$NON-NLS-1$ |
| number.getEndOffset()); |
| return; |
| default: |
| // 0 and 1 are the only allowed digits for binary integers |
| // No floating point, exponents etc. are allowed |
| break loop; |
| } |
| } |
| switch (image[pos]) { |
| // octal digits |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| continue; |
| |
| // decimal digits |
| case '8': |
| case '9': |
| if (isOctal) { |
| handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(), number.getEndOffset()); |
| return; |
| } |
| continue; |
| |
| // hex digits |
| case 'a': |
| case 'A': |
| case 'b': |
| case 'B': |
| case 'c': |
| case 'C': |
| case 'd': |
| case 'D': |
| case 'f': |
| case 'F': |
| if (isHex && !hasExponent) { |
| continue; |
| } |
| break loop; |
| |
| case '.': |
| if (hasDot) { |
| handleProblem(IProblem.SCANNER_BAD_FLOATING_POINT, image, number.getOffset(), |
| number.getEndOffset()); |
| return; |
| } |
| hasDot = true; |
| continue; |
| |
| // C++14 literal separator |
| case '\'': |
| if (fLexOptions.fSupportDigitSeparators) { |
| continue; |
| } else { |
| break loop; |
| } |
| |
| // check for exponent or hex digit |
| case 'E': |
| case 'e': |
| if (isHex && !hasExponent) { |
| continue; |
| } |
| if (isFloat && !isHex && !hasExponent && pos + 1 < image.length) { |
| switch (image[pos + 1]) { |
| case '+': |
| case '-': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| hasExponent = true; |
| ++pos; |
| continue; |
| } |
| } |
| break loop; |
| |
| // check for hex float exponent |
| case 'p': |
| case 'P': |
| if (isFloat && isHex && !hasExponent && pos + 1 < image.length) { |
| switch (image[pos + 1]) { |
| case '+': |
| case '-': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| hasExponent = true; |
| ++pos; |
| continue; |
| } |
| } |
| break loop; |
| |
| default: |
| break loop; |
| } |
| } |
| if (pos < image.length) { |
| char c = image[pos]; |
| if (Character.isLetter(c) || c == '_') { |
| // check the suffix |
| final int suffixStart = pos; |
| loop: for (; pos < image.length; pos++) { |
| c = image[pos]; |
| switch (c) { |
| case 'u': |
| case 'U': |
| case 'L': |
| case 'l': |
| continue; |
| case 'f': |
| case 'F': |
| if (isFloat) { |
| continue loop; |
| } |
| |
| //$FALL-THROUGH$ |
| case 'a': |
| case 'b': |
| case 'c': |
| case 'd': |
| case 'e': |
| case 'g': |
| case 'h': |
| case 'i': |
| case 'j': |
| case 'k': |
| case 'm': |
| case 'n': |
| case 'o': |
| case 'p': |
| case 'q': |
| case 'r': |
| case 's': |
| case 't': |
| case 'v': |
| case 'w': |
| case 'x': |
| case 'y': |
| case 'z': |
| case 'A': |
| case 'B': |
| case 'C': |
| case 'D': |
| case 'E': |
| case 'G': |
| case 'H': |
| case 'I': |
| case 'J': |
| case 'K': |
| case 'M': |
| case 'N': |
| case 'O': |
| case 'P': |
| case 'Q': |
| case 'R': |
| case 'S': |
| case 'T': |
| case 'V': |
| case 'W': |
| case 'X': |
| case 'Y': |
| case 'Z': |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| case '_': |
| if (fLexOptions.fSupportUserDefinedLiterals) { |
| continue loop; |
| } |
| } |
| for (int i = 0; i < fAdditionalNumericLiteralSuffixes.length; i++) { |
| if (fAdditionalNumericLiteralSuffixes[i] == c) { |
| continue loop; |
| } else { |
| badSuffix = true; |
| } |
| } |
| |
| if (badSuffix) { |
| char[] suffix = CharArrayUtils.subarray(image, suffixStart, -1); |
| handleProblem(IProblem.SCANNER_CONSTANT_WITH_BAD_SUFFIX, suffix, number.getOffset(), |
| number.getEndOffset()); |
| return; |
| } |
| } |
| return; |
| } |
| } else { |
| return; |
| } |
| |
| if (isBin) { |
| // The check for bin has to come before float, otherwise binary integers |
| // with float components get flagged as BAD_FLOATING_POINT |
| handleProblem(IProblem.SCANNER_BAD_BINARY_FORMAT, image, number.getOffset(), number.getEndOffset()); |
| } else if (isFloat) { |
| handleProblem(IProblem.SCANNER_BAD_FLOATING_POINT, image, number.getOffset(), number.getEndOffset()); |
| } else if (isHex) { |
| handleProblem(IProblem.SCANNER_BAD_HEX_FORMAT, image, number.getOffset(), number.getEndOffset()); |
| } else if (isOctal) { |
| handleProblem(IProblem.SCANNER_BAD_OCTAL_FORMAT, image, number.getOffset(), number.getEndOffset()); |
| } else { |
| handleProblem(IProblem.SCANNER_BAD_DECIMAL_FORMAT, image, number.getOffset(), number.getEndOffset()); |
| } |
| return; |
| |
| } |
| |
| private <T> T findInclusion(final String includeDirective, final boolean quoteInclude, final boolean includeNext, |
| final String currentFile, final IIncludeFileTester<T> tester) { |
| T reader = null; |
| String absoluteInclusionPath = getAbsoluteInclusionPath(includeDirective, currentFile); |
| if (absoluteInclusionPath != null) { |
| return tester.checkFile(absoluteInclusionPath, false, null); |
| } |
| |
| if (currentFile != null && quoteInclude && !includeNext |
| && !fIncludeSearchPath.isInhibitUseOfCurrentFileDirectory()) { |
| // Check to see if we find a match in the directory of the current file |
| final File currentDir = new File(currentFile).getParentFile(); |
| if (currentDir != null) { |
| final String fileLocation = ScannerUtility.createReconciledPath(currentDir.getAbsolutePath(), |
| includeDirective); |
| reader = tester.checkFile(fileLocation, false, null); |
| if (reader != null) { |
| return reader; |
| } |
| } |
| } |
| |
| // Now we need to search for the file on the include search path. |
| // If this is a include_next directive then the search starts with the directory |
| // in the search path after the one where the current file was found. |
| IncludeSearchPathElement searchAfter = null; |
| if (includeNext && currentFile != null) { |
| searchAfter = fCurrentContext.getFoundOnPath(); |
| if (searchAfter == null) { |
| // the current file was found without search path |
| String directive = fCurrentContext.getFoundViaDirective(); |
| if (directive == null) { |
| directive = new File(currentFile).getName(); |
| } |
| searchAfter = findFileInIncludePath(currentFile, directive); |
| } |
| } |
| |
| for (IncludeSearchPathElement path : fIncludeSearchPath.getElements()) { |
| if (searchAfter != null) { |
| if (searchAfter.equals(path)) { |
| searchAfter = null; |
| } |
| } else if (quoteInclude || !path.isForQuoteIncludesOnly()) { |
| String fileLocation = path.getLocation(includeDirective); |
| if (fileLocation != null) { |
| reader = tester.checkFile(fileLocation, false, path); |
| if (reader != null) { |
| return reader; |
| } |
| } |
| } |
| } |
| if (fIncludeFileResolutionHeuristics != null) { |
| String location = fIncludeFileResolutionHeuristics.findInclusion(includeDirective, currentFile); |
| if (location != null) { |
| return tester.checkFile(location, true, null); |
| } |
| } |
| return null; |
| } |
| |
| public static String getAbsoluteInclusionPath(String includeDirective, String currentFile) { |
| // Filename is an absolute path. |
| if (new File(includeDirective).isAbsolute()) { |
| return includeDirective; |
| } |
| // Filename is a Linux absolute path on a Windows machine. |
| if (File.separatorChar == '\\' && !includeDirective.isEmpty()) { |
| final char firstChar = includeDirective.charAt(0); |
| if (firstChar == '\\' || firstChar == '/') { |
| if (currentFile != null && currentFile.length() > 1 && currentFile.charAt(1) == ':') { |
| return currentFile.substring(0, 2) + includeDirective; |
| } |
| return includeDirective; |
| } |
| } |
| return null; |
| } |
| |
| private IncludeSearchPathElement findFileInIncludePath(String file, String includeDirective) { |
| for (IncludeSearchPathElement path : fIncludeSearchPath.getElements()) { |
| String fileLocation = path.getLocation(includeDirective); |
| if (file.equals(fileLocation)) { |
| return path; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder buffer = new StringBuilder("Scanner @ file:"); //$NON-NLS-1$ |
| buffer.append(fCurrentContext.toString()); |
| buffer.append(" line: "); //$NON-NLS-1$ |
| buffer.append(fLocationMap.getCurrentLineNumber(fCurrentContext.currentLexerToken().getOffset())); |
| return buffer.toString(); |
| } |
| |
| private void addMacroDefinition(IIndexMacro macro) { |
| try { |
| final char[] expansionImage = macro.getExpansionImage(); |
| if (expansionImage == null) { |
| // this is an undef |
| fMacroDictionary.remove(macro.getNameCharArray()); |
| } else { |
| PreprocessorMacro result = MacroDefinitionParser.parseMacroDefinition(macro.getNameCharArray(), |
| macro.getParameterList(), expansionImage); |
| fLocationMap.registerMacroFromIndex(result, macro.getDefinition(), -1); |
| fMacroDictionary.put(result.getNameCharArray(), result); |
| } |
| } catch (Exception e) { |
| fLog.traceLog("Invalid macro definition: '" + macro.getName() + "'"); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| } |
| |
| public ILocationResolver getLocationMap() { |
| return fLocationMap; |
| } |
| |
| @Override |
| public void handleComment(boolean isBlockComment, int offset, int endOffset, AbstractCharArray input) { |
| fLocationMap.encounteredComment(offset, endOffset, isBlockComment, input); |
| } |
| |
| @Override |
| public void handleProblem(int id, char[] arg, int offset, int endOffset) { |
| fLocationMap.encounterProblem(id, arg, offset, endOffset); |
| } |
| |
| /** |
| * Assumes that the pound token has not yet been consumed |
| * @since 5.0 |
| */ |
| private void executeDirective(final Lexer lexer, final int startOffset, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| final Token ident = lexer.nextToken(); |
| switch (ident.getType()) { |
| case IToken.tCOMPLETION: |
| lexer.nextToken(); |
| Token completionToken = new TokenWithImage(ident.getType(), null, startOffset, ident.getEndOffset(), |
| ("#" + ident.getImage()).toCharArray()); //$NON-NLS-1$ |
| throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, completionToken); |
| |
| case Lexer.tNEWLINE: |
| return; |
| |
| case IToken.tEND_OF_INPUT: |
| case IToken.tINTEGER: |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| return; |
| |
| case IToken.tIDENTIFIER: |
| break; |
| |
| default: |
| int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, ident.getCharImage(), startOffset, endOffset); |
| return; |
| } |
| |
| // we have an identifier |
| final char[] name = ident.getCharImage(); |
| final int type = fPPKeywords.get(name); |
| int condEndOffset; |
| switch (type) { |
| case IPreprocessorDirective.ppImport: |
| case IPreprocessorDirective.ppInclude: |
| case IPreprocessorDirective.ppInclude_next: |
| executeInclude(lexer, startOffset, type, fCurrentContext.getCodeState() == CodeState.eActive, |
| withinExpansion); |
| break; |
| case IPreprocessorDirective.ppDefine: |
| CodeState state = fCurrentContext.getCodeState(); |
| if (state == CodeState.eSkipInactive) { |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| } else { |
| executeDefine(lexer, startOffset, state == CodeState.eActive); |
| } |
| break; |
| case IPreprocessorDirective.ppUndef: |
| state = fCurrentContext.getCodeState(); |
| if (state == CodeState.eSkipInactive) { |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| } else { |
| executeUndefine(lexer, startOffset, fCurrentContext.getCodeState() == CodeState.eActive); |
| } |
| break; |
| case IPreprocessorDirective.ppIfdef: |
| if (executeIfdef(lexer, startOffset, false, withinExpansion) == CodeState.eSkipInactive) |
| skipOverConditionalCode(lexer, withinExpansion); |
| break; |
| case IPreprocessorDirective.ppIfndef: |
| if (executeIfdef(lexer, startOffset, true, withinExpansion) == CodeState.eSkipInactive) |
| skipOverConditionalCode(lexer, withinExpansion); |
| break; |
| case IPreprocessorDirective.ppIf: |
| if (executeIf(lexer, startOffset, false, withinExpansion) == CodeState.eSkipInactive) |
| skipOverConditionalCode(lexer, withinExpansion); |
| break; |
| case IPreprocessorDirective.ppElif: |
| if (executeIf(lexer, startOffset, true, withinExpansion) == CodeState.eSkipInactive) { |
| skipOverConditionalCode(lexer, withinExpansion); |
| } |
| break; |
| case IPreprocessorDirective.ppElse: |
| if (executeElse(lexer, startOffset, withinExpansion) == CodeState.eSkipInactive) { |
| skipOverConditionalCode(lexer, withinExpansion); |
| } |
| break; |
| case IPreprocessorDirective.ppEndif: |
| executeEndif(lexer, startOffset, withinExpansion); |
| break; |
| case IPreprocessorDirective.ppWarning: |
| case IPreprocessorDirective.ppError: |
| int condOffset = lexer.nextToken().getOffset(); |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| // Missing argument |
| if (condEndOffset < condOffset) { |
| condOffset = condEndOffset; |
| } |
| if (fCurrentContext.getCodeState() == CodeState.eActive) { |
| int endOffset = lexer.currentToken().getEndOffset(); |
| final char[] warning = lexer.getInputChars(condOffset, condEndOffset); |
| final int id = type == IPreprocessorDirective.ppError ? IProblem.PREPROCESSOR_POUND_ERROR |
| : IProblem.PREPROCESSOR_POUND_WARNING; |
| handleProblem(id, warning, condOffset, condEndOffset); |
| fLocationMap.encounterPoundError(startOffset, condOffset, condEndOffset, endOffset); |
| } |
| break; |
| case IPreprocessorDirective.ppPragma: |
| Token pragmaToken = lexer.nextToken(); |
| condOffset = pragmaToken.getOffset(); |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| // Missing argument |
| if (condEndOffset < condOffset) { |
| condOffset = condEndOffset; |
| } |
| if (fCurrentContext.getCodeState() == CodeState.eActive) { |
| int endOffset = lexer.currentToken().getEndOffset(); |
| fLocationMap.encounterPoundPragma(startOffset, condOffset, condEndOffset, endOffset); |
| if (CharArrayUtils.equals(ONCE, pragmaToken.getCharImage())) { |
| IFileNomination nom = fLocationMap.reportPragmaOnceSemantics(fCurrentContext.getLocationCtx()); |
| fFileContentProvider.reportPragmaOnceSemantics(getCurrentFilename(), nom); |
| } |
| } |
| break; |
| case IPreprocessorDirective.ppIgnore: |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| break; |
| default: |
| int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| if (fCurrentContext.getCodeState() == CodeState.eActive) { |
| handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, ident.getCharImage(), startOffset, endOffset); |
| } |
| break; |
| } |
| } |
| |
| private void executeInclude(final Lexer lexer, int poundOffset, int includeType, boolean active, |
| boolean withinExpansion) throws OffsetLimitReachedException { |
| // Make sure to clear the extern include guard. |
| final char[] externGuard = fExternIncludeGuard; |
| fExternIncludeGuard = null; |
| |
| if (withinExpansion) { |
| final char[] name = lexer.currentToken().getCharImage(); |
| final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, name, poundOffset, endOffset); |
| return; |
| } |
| |
| lexer.setInsideIncludeDirective(true); |
| final Token header = lexer.nextToken(); |
| lexer.setInsideIncludeDirective(false); |
| |
| int condEndOffset = header.getEndOffset(); |
| final int[] nameOffsets = new int[] { header.getOffset(), condEndOffset }; |
| char[] headerName = null; |
| boolean userInclude = true; |
| |
| switch (header.getType()) { |
| case Lexer.tSYSTEM_HEADER_NAME: |
| userInclude = false; |
| headerName = extractHeaderName(header.getCharImage(), '<', '>', nameOffsets); |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| break; |
| |
| case Lexer.tQUOTE_HEADER_NAME: |
| headerName = extractHeaderName(header.getCharImage(), '"', '"', nameOffsets); |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| break; |
| |
| case IToken.tCOMPLETION: |
| throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, header); |
| |
| case IToken.tIDENTIFIER: |
| TokenList tl = new TokenList(); |
| condEndOffset = nameOffsets[1] = getTokensWithinPPDirective(false, tl, false); |
| Token t = tl.first(); |
| if (t != null) { |
| switch (t.getType()) { |
| case IToken.tSTRING: |
| headerName = extractHeaderName(t.getCharImage(), '"', '"', new int[] { 0, 0 }); |
| break; |
| case IToken.tLT: |
| userInclude = false; |
| boolean complete = false; |
| StringBuilder buf = new StringBuilder(); |
| t = (Token) t.getNext(); |
| while (t != null) { |
| if (t.getType() == IToken.tGT) { |
| complete = true; |
| break; |
| } |
| buf.append(t.getImage()); |
| t = (Token) t.getNext(); |
| } |
| if (complete) { |
| headerName = new char[buf.length()]; |
| buf.getChars(0, buf.length(), headerName, 0); |
| } |
| } |
| } |
| break; |
| |
| default: |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| break; |
| } |
| |
| if (headerName == null || headerName.length == 0) { |
| if (active) { |
| handleProblem(IProblem.PREPROCESSOR_INVALID_DIRECTIVE, lexer.getInputChars(poundOffset, condEndOffset), |
| poundOffset, condEndOffset); |
| } |
| return; |
| } |
| |
| if (includeType == IPreprocessorDirective.ppImport) { |
| // Imports are executed only once. |
| // See http://gcc.gnu.org/onlinedocs/gcc-3.2/cpp/Obsolete-once-only-headers.html. |
| if (fImports != null && fImports.containsKey(headerName)) |
| return; |
| if (active) { |
| if (fImports == null) |
| fImports = new CharArraySet(0); |
| fImports.put(headerName); |
| } |
| } |
| |
| if ((active && fCurrentContext.getDepth() == MAX_INCLUSION_DEPTH) |
| || (fPreventInclusion != null && fPreventInclusion.containsKey(headerName))) { |
| handleProblem(IProblem.PREPROCESSOR_EXCEEDS_MAXIMUM_INCLUSION_DEPTH, |
| lexer.getInputChars(poundOffset, condEndOffset), poundOffset, condEndOffset); |
| if (fPreventInclusion == null) |
| fPreventInclusion = new CharArraySet(0); |
| fPreventInclusion.put(headerName); |
| return; |
| } |
| |
| boolean includeNext = includeType == IPreprocessorDirective.ppInclude_next; |
| final String includeDirective = new String(headerName); |
| if (!active) { |
| // Inactive include |
| String path = null; |
| boolean isHeuristic = false; |
| IFileNomination nominationDelegate = null; |
| |
| if (externGuard != null) { |
| // #ifndef GUARD |
| // #include "file.h" |
| // #endif |
| // When the extern guard matches we need to resolve the inclusion. We don't actually |
| // check whether the guard matches. |
| final IncludeResolution resolved = findInclusion(includeDirective, userInclude, includeNext, |
| getCurrentFilename(), createPathTester); |
| if (resolved != null) { |
| nominationDelegate = fFileContentProvider.isIncludedWithPragmaOnceSemantics(resolved.fLocation); |
| if (nominationDelegate != null) { |
| path = resolved.fLocation; |
| isHeuristic = resolved.fHeuristic; |
| } |
| } |
| } |
| fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset, headerName, |
| path, userInclude, active, isHeuristic, nominationDelegate); |
| return; |
| } |
| |
| // Active include |
| final InternalFileContent fi = findInclusion(includeDirective, userInclude, includeNext, getCurrentFilename(), |
| createCodeReaderTester); |
| if (fi == null) { |
| // Unresolved active include |
| final int len = headerName.length + 2; |
| StringBuilder name = new StringBuilder(len); |
| name.append(userInclude ? '"' : '<'); |
| name.append(headerName); |
| name.append(userInclude ? '"' : '>'); |
| |
| final char[] nameChars = new char[len]; |
| name.getChars(0, len, nameChars, 0); |
| handleProblem(IProblem.PREPROCESSOR_INCLUSION_NOT_FOUND, nameChars, poundOffset, condEndOffset); |
| fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset, headerName, |
| null, userInclude, active, false, null); |
| return; |
| } |
| |
| // Resolved active include |
| final String path = fi.getFileLocation(); |
| final boolean isHeuristic = fi.isFoundByHeuristics(); |
| final boolean pragmaOnceContext = fCurrentContext.isPragmaOnce(); |
| |
| IFileNomination nominationDelegate = null; |
| ASTInclusionStatement stmt = null; |
| List<ISignificantMacros> loadedVerisons = null; |
| switch (fi.getKind()) { |
| case FOUND_IN_INDEX: |
| // Pulled in from index |
| nominationDelegate = fi.getFilesIncluded().get(0); |
| try { |
| ISignificantMacros sm = nominationDelegate.getSignificantMacros(); |
| fCurrentContext.addSignificantMacros(sm); |
| if (pragmaOnceContext && !nominationDelegate.hasPragmaOnceSemantics()) |
| loadedVerisons = fFileContentProvider.getLoadedVersions(path); |
| } catch (CoreException e) { |
| } |
| |
| processInclusionFromIndex(poundOffset, fi, true); |
| break; |
| case USE_SOURCE: |
| // Will be parsed |
| AbstractCharArray source = fi.getSource(); |
| if (source != null) { |
| ILocationCtx ctx = fLocationMap.pushInclusion(poundOffset, nameOffsets[0], nameOffsets[1], |
| condEndOffset, source, path, headerName, userInclude, isHeuristic, fi.isSource()); |
| ScannerContext fctx = new ScannerContext(ctx, fCurrentContext, |
| new Lexer(source, fLexOptions, this, this)); |
| fctx.setFoundOnPath(fi.getFoundOnPath(), includeDirective); |
| detectIncludeGuard(path, source, fctx); |
| fCurrentContext = fctx; |
| stmt = ctx.getInclusionStatement(); |
| stmt.setIncludedFileTimestamp(fi.getTimestamp()); |
| stmt.setIncludedFileSize(fi.getFileSize()); |
| stmt.setIncludedFileContentsHash(source.getContentsHash()); |
| stmt.setIncludedFileReadTime(fi.getReadTime()); |
| stmt.setErrorInIncludedFile(source.hasError()); |
| if (!fCurrentContext.isPragmaOnce()) { |
| // Track the loaded version count, even in a non-pragma-once context. |
| loadedVerisons = fFileContentProvider.getLoadedVersions(path); |
| fctx.setLoadedVersionCount(loadedVerisons.size()); |
| } |
| } |
| fLocationMap.parsingFile(fFileContentProvider, fi); |
| break; |
| |
| case SKIP_FILE: |
| // Already included or fast parsing mode. |
| break; |
| } |
| if (stmt == null) { |
| // Found in index or skipped. |
| stmt = fLocationMap.encounterPoundInclude(poundOffset, nameOffsets[0], nameOffsets[1], condEndOffset, |
| headerName, path, userInclude, active, isHeuristic, nominationDelegate); |
| } |
| // In a pragma once context store loaded versions of this non-pragma-once include |
| if (pragmaOnceContext && loadedVerisons != null && !loadedVerisons.isEmpty()) { |
| stmt.setLoadedVersions(loadedVerisons.toArray(new ISignificantMacros[loadedVerisons.size()])); |
| } |
| } |
| |
| private void processInclusionFromIndex(int offset, InternalFileContent fi, boolean updateContext) { |
| List<IIndexMacro> mdefs = fi.getMacroDefinitions(); |
| for (IIndexMacro macro : mdefs) { |
| addMacroDefinition(macro); |
| if (updateContext) |
| fCurrentContext.internalModification(macro.getNameCharArray()); |
| } |
| for (FileVersion version : fi.getNonPragmaOnceVersions()) { |
| fFileContentProvider.addLoadedVersions(version.fPath, Integer.MAX_VALUE, version.fSigMacros); |
| } |
| fLocationMap.skippedFile(fLocationMap.getSequenceNumberForOffset(offset), fi); |
| } |
| |
| private char[] extractHeaderName(final char[] image, final char startDelim, final char endDelim, int[] offsets) { |
| char[] headerName; |
| int start = 0; |
| int length = image.length; |
| if (length > 0 && image[length - 1] == endDelim) { |
| length--; |
| offsets[1]--; |
| if (length > 0 && image[0] == startDelim) { |
| offsets[0]++; |
| start++; |
| length--; |
| } |
| } |
| headerName = new char[length]; |
| System.arraycopy(image, start, headerName, 0, length); |
| return headerName; |
| } |
| |
| private void executeDefine(final Lexer lexer, int startOffset, boolean isActive) |
| throws OffsetLimitReachedException { |
| try { |
| ObjectStyleMacro macrodef = fMacroDefinitionParser.parseMacroDefinition(lexer, this); |
| if (isActive) { |
| final char[] macroName = macrodef.getNameCharArray(); |
| fMacroDictionary.put(macroName, macrodef); |
| fCurrentContext.internalModification(macroName); |
| } |
| |
| final Token name = fMacroDefinitionParser.getNameToken(); |
| fLocationMap.encounterPoundDefine(startOffset, name.getOffset(), name.getEndOffset(), |
| macrodef.getExpansionOffset(), macrodef.getExpansionEndOffset(), isActive, macrodef); |
| } catch (InvalidMacroDefinitionException e) { |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| handleProblem(IProblem.PREPROCESSOR_INVALID_MACRO_DEFN, e.fName, e.fStartOffset, e.fEndOffset); |
| } |
| } |
| |
| private void executeUndefine(Lexer lexer, int startOffset, boolean isActive) throws OffsetLimitReachedException { |
| final Token name = lexer.nextToken(); |
| final int tt = name.getType(); |
| if (tt != IToken.tIDENTIFIER) { |
| if (tt == IToken.tCOMPLETION) { |
| throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, name); |
| } |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| handleProblem(IProblem.PREPROCESSOR_INVALID_MACRO_DEFN, name.getCharImage(), startOffset, |
| name.getEndOffset()); |
| return; |
| } |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| final int endOffset = lexer.currentToken().getEndOffset(); |
| final char[] namechars = name.getCharImage(); |
| PreprocessorMacro definition; |
| if (isActive) { |
| definition = fMacroDictionary.remove(namechars, 0, namechars.length); |
| fCurrentContext.internalModification(namechars); |
| } else { |
| definition = fMacroDictionary.get(namechars); |
| } |
| fLocationMap.encounterPoundUndef(definition, startOffset, name.getOffset(), name.getEndOffset(), endOffset, |
| namechars, isActive); |
| } |
| |
| private CodeState executeIfdef(Lexer lexer, int offset, boolean isIfndef, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| final Token name = lexer.nextToken(); |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| final int tt = name.getType(); |
| final int nameOffset = name.getOffset(); |
| final int nameEndOffset = name.getEndOffset(); |
| final int endOffset = lexer.currentToken().getEndOffset(); |
| |
| boolean isTaken = false; |
| PreprocessorMacro macro = null; |
| final Conditional conditional = fCurrentContext.newBranch(BranchKind.eIf, withinExpansion); |
| if (conditional.canHaveActiveBranch(withinExpansion)) { |
| // we need an identifier |
| if (tt != IToken.tIDENTIFIER) { |
| if (tt == IToken.tCOMPLETION) { |
| throw new OffsetLimitReachedException(ORIGIN_PREPROCESSOR_DIRECTIVE, name); |
| } |
| // report problem and treat as inactive |
| handleProblem(IProblem.PREPROCESSOR_DEFINITION_NOT_FOUND, name.getCharImage(), offset, nameEndOffset); |
| } else { |
| final char[] namechars = name.getCharImage(); |
| if (isIfndef) { |
| if (IncludeGuardDetection.detectIncludeEndif(lexer)) { |
| fExternIncludeGuard = namechars; |
| } |
| } |
| macro = fMacroDictionary.get(namechars); |
| isTaken = (macro == null) == isIfndef; |
| if (macro == null) { |
| macro = new UndefinedMacro(namechars); |
| fCurrentContext.significantMacroUndefined(namechars); |
| } else { |
| fCurrentContext.significantMacroDefined(namechars); |
| } |
| } |
| } |
| |
| ASTPreprocessorNode stmt; |
| if (isIfndef) { |
| stmt = fLocationMap.encounterPoundIfndef(offset, nameOffset, nameEndOffset, endOffset, isTaken, macro); |
| } else { |
| stmt = fLocationMap.encounterPoundIfdef(offset, nameOffset, nameEndOffset, endOffset, isTaken, macro); |
| } |
| if (!conditional.isActive(withinExpansion)) |
| stmt.setInactive(); |
| |
| return fCurrentContext.setBranchState(conditional, isTaken, withinExpansion, offset); |
| } |
| |
| private CodeState executeIf(Lexer lexer, int startOffset, boolean isElif, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| Conditional cond = fCurrentContext.newBranch(isElif ? BranchKind.eElif : BranchKind.eIf, withinExpansion); |
| if (cond == null) { |
| char[] name = lexer.currentToken().getCharImage(); |
| int condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, name, startOffset, condEndOffset); |
| return fCurrentContext.getCodeState(); |
| } |
| |
| boolean isTaken = false; |
| IASTName[] refs = IASTName.EMPTY_NAME_ARRAY; |
| int condOffset = lexer.nextToken().getOffset(); |
| int condEndOffset, endOffset; |
| |
| if (cond.canHaveActiveBranch(withinExpansion)) { |
| char[] macro = IncludeGuardDetection.detectIfNotDefinedIncludeEndif(lexer); |
| if (macro != null) { |
| fExternIncludeGuard = macro; |
| } |
| |
| TokenList condition = new TokenList(); |
| condEndOffset = getTokensWithinPPDirective(true, condition, withinExpansion); |
| endOffset = lexer.currentToken().getEndOffset(); |
| |
| if (condition.first() == null) { |
| handleProblem(IProblem.SCANNER_EXPRESSION_SYNTAX_ERROR, null, startOffset, endOffset); |
| } else { |
| try { |
| fExpressionEvaluator.clearMacrosInDefinedExpression(); |
| isTaken = fExpressionEvaluator.evaluate(condition, fMacroDictionary, fLocationMap); |
| refs = fExpressionEvaluator.clearMacrosInDefinedExpression(); |
| for (IASTName iastName : refs) { |
| IBinding mb = iastName.getBinding(); |
| if (mb instanceof UndefinedMacro) { |
| fCurrentContext.significantMacroUndefined(iastName.toCharArray()); |
| } else { |
| fCurrentContext.significantMacroDefined(iastName.toCharArray()); |
| } |
| } |
| } catch (EvalException e) { |
| handleProblem(e.getProblemID(), e.getProblemArg(), condOffset, endOffset); |
| } |
| } |
| } else { |
| condEndOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| endOffset = lexer.currentToken().getEndOffset(); |
| } |
| |
| ASTPreprocessorNode stmt; |
| if (isElif) { |
| stmt = fLocationMap.encounterPoundElif(startOffset, condOffset, condEndOffset, endOffset, isTaken, refs); |
| } else { |
| stmt = fLocationMap.encounterPoundIf(startOffset, condOffset, condEndOffset, endOffset, isTaken, refs); |
| } |
| if (!cond.isActive(withinExpansion)) |
| stmt.setInactive(); |
| |
| return fCurrentContext.setBranchState(cond, isTaken, withinExpansion, startOffset); |
| } |
| |
| private CodeState executeElse(final Lexer lexer, final int startOffset, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| Conditional cond = fCurrentContext.newBranch(BranchKind.eElse, withinExpansion); |
| if (cond == null) { |
| handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, Keywords.cELSE, startOffset, endOffset); |
| return fCurrentContext.getCodeState(); |
| } |
| |
| final boolean isTaken = cond.canHaveActiveBranch(withinExpansion); |
| ASTElse stmt = fLocationMap.encounterPoundElse(startOffset, endOffset, isTaken); |
| if (!cond.isActive(withinExpansion)) |
| stmt.setInactive(); |
| return fCurrentContext.setBranchState(cond, isTaken, withinExpansion, startOffset); |
| } |
| |
| private CodeState executeEndif(Lexer lexer, int startOffset, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| final int endOffset = lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| final Conditional cond = fCurrentContext.newBranch(BranchKind.eEnd, withinExpansion); |
| if (cond == null) { |
| handleProblem(IProblem.PREPROCESSOR_UNBALANCE_CONDITION, Keywords.cENDIF, startOffset, endOffset); |
| } else { |
| ASTEndif stmt = fLocationMap.encounterPoundEndIf(startOffset, endOffset); |
| if (!cond.isActive(withinExpansion)) |
| stmt.setInactive(); |
| } |
| return fCurrentContext.setBranchEndState(cond, withinExpansion, startOffset); |
| } |
| |
| /** |
| * Runs the preprocessor on the rest of the line, storing the tokens in the holder supplied. |
| * Macro expansion is reported to the location map. |
| * In case isCondition is set to <code>true</code>, identifiers with image 'defined' are |
| * converted to the defined-token and its argument is not macro expanded. |
| * Returns the end-offset of the last token that was consumed. |
| */ |
| private int getTokensWithinPPDirective(boolean isCondition, TokenList result, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| final ScannerContext scannerCtx = fCurrentContext; |
| scannerCtx.clearInactiveCodeMarkerToken(); |
| int options = STOP_AT_NL | REPORT_SIGNIFICANT_MACROS; |
| if (isCondition) |
| options |= PROTECT_INTRINSICS; |
| |
| loop: while (true) { |
| Token t = internalFetchToken(scannerCtx, options, withinExpansion); |
| switch (t.getType()) { |
| case IToken.tEND_OF_INPUT: |
| case IToken.tCOMPLETION: |
| scannerCtx.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); // make sure the exception is thrown. |
| break loop; |
| case Lexer.tNEWLINE: |
| break loop; |
| case IToken.tIDENTIFIER: |
| break; |
| case tDEFINED: |
| options |= NO_EXPANSION; |
| break; |
| case t__HAS_FEATURE: |
| options |= NO_EXPANSION; |
| break; |
| case IToken.tLPAREN: |
| break; |
| default: |
| options &= ~NO_EXPANSION; |
| break; |
| } |
| result.append(t); |
| } |
| // make sure an exception is thrown if we are running content assist at the end of the line |
| return scannerCtx.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| } |
| |
| private void skipOverConditionalCode(final Lexer lexer, boolean withinExpansion) |
| throws OffsetLimitReachedException { |
| CodeState state = CodeState.eSkipInactive; |
| while (state == CodeState.eSkipInactive) { |
| state = skipBranch(lexer, withinExpansion); |
| } |
| } |
| |
| private CodeState skipBranch(final Lexer lexer, boolean withinExpansion) throws OffsetLimitReachedException { |
| while (true) { |
| final Token pound = lexer.nextDirective(); |
| int tt = pound.getType(); |
| if (tt != IToken.tPOUND) { |
| if (tt == IToken.tCOMPLETION) { |
| // completion in inactive code |
| throw new OffsetLimitReachedException(ORIGIN_INACTIVE_CODE, pound); |
| } |
| // must be the end of the lexer |
| return CodeState.eActive; |
| } |
| final Token ident = lexer.nextToken(); |
| tt = ident.getType(); |
| if (tt != IToken.tIDENTIFIER) { |
| if (tt == IToken.tCOMPLETION) { |
| // completion in inactive directive |
| throw new OffsetLimitReachedException(ORIGIN_INACTIVE_CODE, ident); |
| } |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| continue; |
| } |
| |
| // we have an identifier |
| final char[] name = ident.getCharImage(); |
| final int type = fPPKeywords.get(name); |
| switch (type) { |
| case IPreprocessorDirective.ppImport: |
| case IPreprocessorDirective.ppInclude: |
| case IPreprocessorDirective.ppInclude_next: |
| executeInclude(lexer, pound.getOffset(), type, false, withinExpansion); |
| break; |
| case IPreprocessorDirective.ppIfdef: |
| return executeIfdef(lexer, pound.getOffset(), false, withinExpansion); |
| case IPreprocessorDirective.ppIfndef: |
| return executeIfdef(lexer, pound.getOffset(), true, withinExpansion); |
| case IPreprocessorDirective.ppIf: |
| return executeIf(lexer, pound.getOffset(), false, withinExpansion); |
| case IPreprocessorDirective.ppElif: |
| return executeIf(lexer, pound.getOffset(), true, withinExpansion); |
| case IPreprocessorDirective.ppElse: |
| return executeElse(lexer, pound.getOffset(), withinExpansion); |
| case IPreprocessorDirective.ppEndif: |
| return executeEndif(lexer, pound.getOffset(), withinExpansion); |
| default: |
| lexer.consumeLine(ORIGIN_PREPROCESSOR_DIRECTIVE); |
| break; |
| } |
| } |
| } |
| |
| /** |
| * The method assumes that the identifier is consumed. |
| * <p> |
| * Checks whether the identifier causes a macro expansion. May advance the current lexer |
| * over newlines to check for the opening bracket succeeding the identifier. |
| * <p> |
| * 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 { |
| final boolean reportSignificant = (options & REPORT_SIGNIFICANT_MACROS) != 0; |
| final char[] name = identifier.getCharImage(); |
| if ((options & PROTECT_INTRINSICS) != 0) { |
| if (CharArrayUtils.equals(name, Keywords.cDEFINED)) { |
| identifier.setType(tDEFINED); |
| return false; |
| } |
| if (CharArrayUtils.equals(name, Keywords.c__HAS_FEATURE)) { |
| identifier.setType(t__HAS_FEATURE); |
| return false; |
| } |
| } |
| PreprocessorMacro macro = fMacroDictionary.get(name); |
| if (macro == null) { |
| if (reportSignificant && (options & IGNORE_UNDEFINED_SIGNIFICANT_MACROS) == 0) |
| fCurrentContext.significantMacroUndefined(name); |
| return false; |
| } |
| boolean stopAtNewline = (options & STOP_AT_NL) != 0; |
| if (macro instanceof FunctionStyleMacro) { |
| Token t = lexer.currentToken(); |
| if (!stopAtNewline) { |
| while (t.getType() == Lexer.tNEWLINE) { |
| t = lexer.nextToken(); |
| } |
| } |
| if (t.getType() != IToken.tLPAREN) { |
| if (reportSignificant) |
| fCurrentContext.significantMacro(macro); |
| return false; |
| } |
| } |
| final boolean contentAssist = fContentAssistLimit >= 0 && fCurrentContext == fRootContext; |
| final ITokenSequence input = stopAtNewline ? fLineInputToMacroExpansion : fInputToMacroExpansion; |
| final MacroExpander expander = withinExpansion |
| ? new MacroExpander(this, fMacroDictionary, fLocationMap, fLexOptions) |
| : fMacroExpander; |
| TokenList replacement = expander.expand(input, options, macro, identifier, contentAssist, fCurrentContext); |
| final IASTName[] expansions = expander.clearImplicitExpansions(); |
| final ImageLocationInfo[] ili = expander.clearImageLocationInfos(); |
| final Token last = replacement.last(); |
| final int length = last == null ? 0 : last.getEndOffset(); |
| ILocationCtx ctx = fLocationMap.pushMacroExpansion(identifier.getOffset(), identifier.getEndOffset(), |
| lexer.getLastEndOffset(), length, macro, expansions, ili); |
| fCurrentContext = new ScannerContext(ctx, fCurrentContext, replacement); |
| return true; |
| } |
| |
| /** |
| * Returns the user-defined suffix of a user-define string literal. |
| * |
| * @param token |
| * @return the suffix of the token, if it exists |
| */ |
| private static char[] getUserDefinedLiteralSuffix(IToken token) { |
| if (token.getType() != IToken.tUSER_DEFINED_STRING_LITERAL) |
| throw new IllegalArgumentException(); |
| |
| char[] image = token.getCharImage(); |
| int offset = CharArrayUtils.lastIndexOf('"', image) + 1; |
| if (offset <= 0) |
| throw new IllegalArgumentException(); |
| if (offset == image.length) |
| return CharArrayUtils.EMPTY_CHAR_ARRAY; |
| return CharArrayUtils.subarray(image, offset, image.length); |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> T getAdapter(Class<T> adapter) { |
| if (adapter.isAssignableFrom(fMacroExpander.getClass())) { |
| return (T) fMacroExpander; |
| } |
| return null; |
| } |
| |
| /** |
| * Return whether 'name' is a macro whose definition is provided by the |
| * preprocessor, like __LINE__, __FILE__, __DATE__ or __TIME__. |
| */ |
| public static boolean isPreprocessorProvidedMacro(char[] name) { |
| return CharArrayUtils.equals(__LINE__.getNameCharArray(), name) |
| || CharArrayUtils.equals(__FILE__.getNameCharArray(), name) |
| || CharArrayUtils.equals(__DATE__.getNameCharArray(), name) |
| || CharArrayUtils.equals(__TIME__.getNameCharArray(), name); |
| } |
| |
| @SuppressWarnings("nls") |
| public Set<String> getSupportedFeatures() { |
| if (sSupportedFeatures == null) { |
| sSupportedFeatures = new HashSet<>(); |
| |
| // C++98 features |
| sSupportedFeatures.add("cxx_exceptions"); |
| sSupportedFeatures.add("cxx_rtti"); |
| |
| // C++11 features |
| // missing: cxx_access_control_sfinae (needs to be tested) |
| sSupportedFeatures.add("cxx_alias_templates"); |
| sSupportedFeatures.add("cxx_alignas"); |
| sSupportedFeatures.add("cxx_alignof"); |
| sSupportedFeatures.add("cxx_atomic"); |
| sSupportedFeatures.add("cxx_attributes"); |
| sSupportedFeatures.add("cxx_auto_type"); |
| sSupportedFeatures.add("cxx_constexpr"); |
| sSupportedFeatures.add("cxx_decltype"); |
| // missing: cxx_decltype_incomplete_return_types (needs to be tested; see bug 324096) |
| sSupportedFeatures.add("cxx_default_function_template_args"); |
| sSupportedFeatures.add("cxx_defaulted_functions"); |
| sSupportedFeatures.add("cxx_delegating_constructors"); |
| sSupportedFeatures.add("cxx_deleted_functions"); |
| sSupportedFeatures.add("cxx_explicit_conversions"); |
| sSupportedFeatures.add("cxx_generalized_initializers"); |
| // missing: cxx_implicit_moves (bug 327301) |
| sSupportedFeatures.add("cxx_inheriting_constructors"); |
| sSupportedFeatures.add("cxx_inline_namespaces"); |
| sSupportedFeatures.add("cxx_lambdas"); |
| sSupportedFeatures.add("cxx_local_type_template_args"); |
| sSupportedFeatures.add("cxx_noexcept"); |
| sSupportedFeatures.add("cxx_nonstatic_member_init"); |
| sSupportedFeatures.add("cxx_nullptr"); |
| sSupportedFeatures.add("cxx_override_control"); |
| sSupportedFeatures.add("cxx_range_for"); |
| sSupportedFeatures.add("cxx_raw_string_literals"); |
| sSupportedFeatures.add("cxx_reference_qualified_functions"); |
| sSupportedFeatures.add("cxx_rvalue_references"); |
| sSupportedFeatures.add("cxx_static_assert"); |
| sSupportedFeatures.add("cxx_strong_enums"); |
| sSupportedFeatures.add("cxx_thread_local"); |
| sSupportedFeatures.add("cxx_trailing_return"); |
| sSupportedFeatures.add("cxx_unicode_literals"); |
| // missing: cxx_unrestricted_unions (bug 327299) |
| sSupportedFeatures.add("cxx_user_literals"); |
| sSupportedFeatures.add("cxx_variadic_templates"); |
| |
| // C++14 features |
| // missing: cxx_aggregate_nsdmi |
| // missing: cxx_binary_literals |
| // missing: cxx_contextual_conversions |
| // missing: cxx_decltype_auto (bug 408470) |
| // missing: cxx_generic_lambdas |
| // missing: cxx_init_captures (bug 413527) |
| sSupportedFeatures.add("cxx_relaxed_constexpr"); |
| // missing: cxx_return_type_deduction (bug 408470) |
| sSupportedFeatures.add("cxx_variable_templates"); |
| |
| // C11 features |
| sSupportedFeatures.add("c_alignas"); |
| sSupportedFeatures.add("c_alignof"); |
| // missing: c_atomic (bug 445297) |
| // missing: c_generic_selections (bug 445296) |
| // missing: c_static_assert (bug 445297) |
| // missing: c_thread_local (bug 445297) |
| |
| // Type trait primitives |
| // Whether support for these is activated depends on the scanner extension |
| // configuration, so check it and report support accordingly. |
| // Note also that having a keyword for it doesn't necessarily mean we fully |
| // support it. For example, currently we have a keyword |
| // GCCKeywords.cp__has_nothrown_assign, but we don't support evaluation of |
| // this trait at the semantics level, so we don't report support for it. |
| // missing: has_nothrow_assign |
| // missing: has_nothrow_copy |
| // missing: has_nothrow_constructor |
| // missing: has_trivial_assign |
| addTypeTraitPrimitive("has_trivial_copy", GCCKeywords.cp__has_trivial_copy); |
| // missing: has_trivial_constructor |
| // missing: has_trivial_destructor |
| // missing: has_virtual_destructor |
| addTypeTraitPrimitive("is_abstract", GCCKeywords.cp__is_abstract); |
| addTypeTraitPrimitive("is_base_of", GCCKeywords.cp__is_base_of); |
| addTypeTraitPrimitive("is_class", GCCKeywords.cp__is_class); |
| addTypeTraitPrimitive("is_constructible", GCCKeywords.cp__is_constructible); |
| // missing: is_convertible_to |
| // missing: is_destructible |
| addTypeTraitPrimitive("is_empty", GCCKeywords.cp__is_empty); |
| addTypeTraitPrimitive("is_enum", GCCKeywords.cp__is_enum); |
| addTypeTraitPrimitive("is_final", GCCKeywords.cp__is_final); |
| // missing: is_interface_class |
| // missing: is_literal |
| // missing: is_nothrow_assignable |
| // missing: is_nothrow_constructible |
| // missing: is_nothrow_destructible |
| addTypeTraitPrimitive("is_pod", GCCKeywords.cp__is_pod); |
| addTypeTraitPrimitive("is_polymorphic", GCCKeywords.cp__is_polymorphic); |
| addTypeTraitPrimitive("is_standard_layout", GCCKeywords.cp__is_standard_layout); |
| addTypeTraitPrimitive("is_trivial", GCCKeywords.cp__is_trivial); |
| // missing: is_trivially_assignable |
| // missing: is_trivially_constructible |
| addTypeTraitPrimitive("is_trivially_copyable", GCCKeywords.cp__is_trivially_copyable); |
| addTypeTraitPrimitive("is_union", GCCKeywords.cp__is_union); |
| addTypeTraitPrimitive("underlying_type", GCCKeywords.cp__underlying_type); |
| } |
| return sSupportedFeatures; |
| } |
| |
| private void addTypeTraitPrimitive(String featureName, char[] keyword) { |
| if (fKeywords.containsKey(keyword)) { |
| sSupportedFeatures.add(featureName); |
| } |
| } |
| } |