001package eu.cqse.check.framework.util.cs; 002 003import static eu.cqse.check.framework.scanner.ETokenType.LPAREN; 004import static eu.cqse.check.framework.scanner.ETokenType.MULTILINE_COMMENT; 005import static eu.cqse.check.framework.scanner.ETokenType.STRING_LITERAL; 006 007import java.util.ArrayList; 008import java.util.Arrays; 009import java.util.EnumSet; 010import java.util.HashSet; 011import java.util.List; 012import java.util.Set; 013import java.util.regex.Matcher; 014import java.util.regex.Pattern; 015import java.util.stream.Collectors; 016 017import org.conqat.lib.commons.collections.SetMap; 018 019import eu.cqse.check.framework.scanner.ELanguage; 020import eu.cqse.check.framework.scanner.ETokenType; 021import eu.cqse.check.framework.scanner.IToken; 022import eu.cqse.check.framework.scanner.ScannerUtils; 023import eu.cqse.check.framework.shallowparser.framework.ShallowEntity; 024import eu.cqse.check.framework.shallowparser.languages.cs.CsShallowParser; 025import eu.cqse.check.framework.util.clike.CLikeCheckUtils; 026import eu.cqse.check.framework.util.tokens.TokenPattern; 027import eu.cqse.check.framework.util.tokens.TokenPatternMatch; 028 029/** Utility methods for analyzing C# code */ 030public abstract class CsCheckUtils { 031 032 /** 033 * Matches an interpolation in a C# 6 interpolation string. 034 */ 035 public static final Pattern INTERPOLATION_PATTERN = Pattern.compile("\\{([^}]*)[^}]*\\}"); 036 037 /** 038 * Value for a unknown method arity. This value is used if a method name is 039 * found in a method reference. 040 */ 041 public static final int UNKNOWN_ARITY = -1; 042 private static final String MAIN_METHOD_NAME = "Main"; 043 private static final String TASK_TYPE_NAME = "Task"; 044 045 /** 046 * The index for the group of the pattern 047 * {@link #POSSIBLE_METHOD_REFERENCE_PATTERN} which contains the actual 048 * identifier. 049 */ 050 private static final int REFERENCE_GROUP_INDEX = 0; 051 052 /** 053 * Pattern that matches a possible reference to a method. 054 * 055 * A method reference here means any instance where a method is basically 056 * handled like an object or a reference, so any actual method invocation is not 057 * included. 058 * 059 * This means that we match any identifier which is NOT followed by a left 060 * paren. 061 */ 062 private static final TokenPattern POSSIBLE_METHOD_REFERENCE_PATTERN = new TokenPattern() 063 .sequence(CsShallowParser.VALID_IDENTIFIERS).group(REFERENCE_GROUP_INDEX) 064 .sequence(EnumSet.complementOf(EnumSet.of(LPAREN))); 065 066 /** 067 * Determines, if the given method is a valid Main method according to the 068 * <a href= 069 * "https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/main-and-command-args/">Microsoft 070 * documentation</a> 071 */ 072 public static boolean isMainMethod(ShallowEntity method) { 073 if (!MAIN_METHOD_NAME.equals(method.getName())) { 074 return false; 075 } 076 077 boolean hasCorrectMainMethodArguments = hasCorrectMainMethodArguments(method); 078 if (!hasCorrectMainMethodArguments) { 079 return false; 080 } 081 082 return hasCorrectMainMethodReturnTypeAndModifiers(method); 083 } 084 085 private static boolean hasCorrectMainMethodArguments(ShallowEntity method) { 086 List<IToken> argumentList = CLikeCheckUtils.getMethodArguments(method); 087 List<ETokenType> argTypes = argumentList.stream().map(IToken::getType).collect(Collectors.toList()); 088 089 // Arguments can either be 090 // empty, 091 if (argumentList.size() == 0) { 092 return true; 093 } 094 095 // string[] args, 096 if (argumentList.size() == 4) { 097 List validTokens = Arrays.asList(ETokenType.STRING, ETokenType.LBRACK, ETokenType.RBRACK, 098 ETokenType.IDENTIFIER); 099 return argTypes.equals(validTokens); 100 } 101 102 // or params string[] args 103 if (argumentList.size() == 5) { 104 List validTokens = Arrays.asList(ETokenType.PARAMS, ETokenType.STRING, ETokenType.LBRACK, ETokenType.RBRACK, 105 ETokenType.IDENTIFIER); 106 return argTypes.equals(validTokens); 107 } 108 109 return false; 110 } 111 112 private static boolean hasCorrectMainMethodReturnTypeAndModifiers(ShallowEntity method) { 113 List<IToken> modifierReturnTokens = CLikeCheckUtils.getMethodModifiersAndReturnType(method); 114 115 // Filter brackets and other irrelevant tokens 116 modifierReturnTokens = modifierReturnTokens.stream() 117 .filter(token -> ETokenType.ETokenClass.KEYWORD == token.getType().getTokenClass() 118 || (ETokenType.ETokenClass.IDENTIFIER == token.getType().getTokenClass())) 119 .collect(Collectors.toList()); 120 121 boolean isStatic = modifierReturnTokens.stream().anyMatch(token -> ETokenType.STATIC == token.getType()); 122 if (!isStatic) { 123 return false; 124 } 125 126 Set<ETokenType> validModifierReturnTokens = new HashSet<>( 127 Arrays.asList(ETokenType.PRIVATE, ETokenType.PROTECTED, ETokenType.PUBLIC, ETokenType.INTERNAL, 128 ETokenType.STATIC, ETokenType.VOID, ETokenType.INT)); 129 130 // The async keyword is only allowed if the main method has return type Task 131 boolean hasReturnTypeTask = modifierReturnTokens.stream() 132 .filter(token -> token.getType().isIdentifier() && TASK_TYPE_NAME.equals(token.getText())) 133 .collect(Collectors.toList()).size() > 0; 134 if (hasReturnTypeTask) { 135 validModifierReturnTokens.add(ETokenType.ASYNC); 136 } 137 138 return modifierReturnTokens.stream().allMatch(token -> validModifierReturnTokens.contains(token.getType()) 139 || (ETokenType.IDENTIFIER == token.getType() && TASK_TYPE_NAME.equals(token.getText()))); 140 } 141 142 /** 143 * Retrieves all possible method references from the given tokens and adds them 144 * to the given SetMap. 145 * 146 * A method reference here means any instance where a method is basically 147 * handled like an object or a reference, so any actual method invocation is not 148 * included. 149 */ 150 public static SetMap<String, Integer> retrieveMethodReferences(List<IToken> tokens) { 151 SetMap<String, Integer> result = new SetMap<>(); 152 for (TokenPatternMatch match : POSSIBLE_METHOD_REFERENCE_PATTERN.findAll(tokens)) { 153 result.add(match.groupString(REFERENCE_GROUP_INDEX), UNKNOWN_ARITY); 154 } 155 return result; 156 } 157 158 /** 159 * Filters a list of tokens for interpolated literal calls. 160 * <ul> 161 * <li>Example of an interpolated literal: 162 * <code>String string = $"{foo(a, b)} comment";</code></li> 163 * </ul> 164 * 165 * @param tokens 166 * to check for interpolated literal tokens. 167 * @return interpolated literal tokens. 168 */ 169 public static List<IToken> extractInterpolatedCalls(List<IToken> tokens) { 170 return tokens.stream() 171 .filter(token -> token.getType() == STRING_LITERAL || token.getType() == MULTILINE_COMMENT) 172 .filter(literal -> literal.getText().startsWith("$")).collect(Collectors.toList()); 173 } 174 175 /** 176 * Tokenize the interpolated calls in string token. 177 * 178 * @param interpolatedStringToken 179 * to be tokenized. 180 * @return interpolated variables and methods separated in a list of tokens. 181 */ 182 public static List<List<IToken>> tokenizeInterpolatedCalls(List<IToken> interpolatedStringTokens) { 183 List<List<IToken>> readVariables = new ArrayList<>(); 184 for (IToken interpolatedStringToken : interpolatedStringTokens) { 185 Matcher matcher = INTERPOLATION_PATTERN.matcher(interpolatedStringToken.getText()); 186 while (matcher.find()) { 187 String interpolation = matcher.group(0); 188 readVariables.add(ScannerUtils.getTokens(interpolation, ELanguage.CS)); 189 } 190 } 191 return readVariables; 192 } 193 194 /** 195 * Checks whether the given variable name was used in a string interpolation in 196 * the given tokens. Returns a list with the indices of its occurrences. 197 */ 198 public static List<Integer> checkForVariableUseInStringInterpolation(List<IToken> tokens, String variableName) { 199 List<Integer> uses = new ArrayList<>(); 200 // Matches simple usages like {variableName}, {variableName++} or 201 // {-variableName}. 202 // Matches usages when the variable is used as method parameter 203 // {methodCall(..,variableName,..)} or 204 // (someObject.methodCall(..,variableName,..)}. 205 // Matches usages when a method is called on the variable 206 // {variableName.methodCall(...)}. 207 Pattern pattern = Pattern.compile("\\{(.*\\W)?" + Pattern.quote(variableName) + "(\\W.*)?\\}"); 208 for (int index = 0; index < tokens.size(); index++) { 209 IToken token = tokens.get(index); 210 if (!token.getText().startsWith("$\"")) { 211 continue; 212 } 213 214 if (pattern.matcher(token.getText()).find()) { 215 uses.add(index); 216 } 217 } 218 return uses; 219 } 220}