/* * Coverity Sonar Plugin * Copyright (c) 2017 Synopsys, Inc * support@coverity.com * * All rights reserved. This program and the accompanying materials are made * available under the terms of the Eclipse Public License v1.0 which * accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. */ package org.sonar.plugins.coverity.util; import org.apache.commons.lang.StringEscapeUtils; import org.apache.commons.lang.StringUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import org.sonar.plugins.coverity.server.CppLanguage; import org.sonar.plugins.coverity.server.InternalRule; import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; public class RulesGenerator { static Map<String, Map<String, List<InternalRule>>> rulesList = new HashMap<String, Map<String, List<InternalRule>>>(); static final String JAVA_LANGUAGE = "java"; static final String CPP_LANGUAGE = CppLanguage.KEY; static final String CS_LANGUAGE = "cs"; static final String JAVASCRIPT_LANGUAGE = "js"; static final String PYTHON_LANGUAGE = "py"; static final String PHP_LANGUAGE = "php"; static final String OBJECTIVE_C = "objective-c"; static final String VULNERABILITY = "VULNERABILITY"; static final String BUG = "BUG"; static String outputFilePath = "src/main/resources/org/sonar/plugins/coverity/server"; /* RulesGenerator is used to generate rules based on the coverity quality checker-properties.json files and Find bug checkers that coverity will understand( required to be named findbugs-checker-properties.json ) The file paths are needed to passed as main method's parameters. */ public static void main(String[] args) throws Exception { File xmlDir = new File(outputFilePath); if (args.length == 0) { System.out.println("Need to provide path to the checker-properties.json files or find bugs checker file"); return; } for (String filePath : args) { File file = new File(filePath); if (filePath.endsWith("findbugs-checker-properties.json")) { generateRulesForFindBugCheckers(file); } else { generateRulesForQualityCheckers(file); } } addNoneSubcategory(); addFallbackRuleForLanguage(); addDifferentOriginRules(); writeRulesToFiles(xmlDir); } public static void generateRulesForQualityCheckers(File jsonFile) throws Exception { JSONParser parser = new JSONParser(); try { Object obj = parser.parse(new InputStreamReader(new FileInputStream(jsonFile.getAbsolutePath()), StandardCharsets.UTF_8)); JSONArray jsonObject = (JSONArray) obj; Iterator<JSONObject> iterator = jsonObject.iterator(); while( iterator.hasNext() ) { JSONObject childJSON = (JSONObject)iterator.next(); String checkerName = (String) childJSON.get("checkerName"); String subcategory = (String) childJSON.get("subcategory"); String impact = (String) childJSON.get("impact"); String subcategoryLongDescription = (String) childJSON.get("subcategoryLongDescription"); List<String> families = (ArrayList<String>) childJSON.get("families"); String domain = (String) childJSON.get("domain"); String language = (String) childJSON.get("language"); String category = (String) childJSON.get("category"); String subcategoryShortDescription = (String) childJSON.get("subcategoryShortDescription"); String name = category + " : " + subcategoryShortDescription; String key = checkerName + "_" + subcategory; boolean qualityKind; boolean securityKind; Object quality = childJSON.get("qualityKind"); if (quality instanceof String) { qualityKind = Boolean.parseBoolean((String) quality); }else { qualityKind = (boolean) quality; } Object security = childJSON.get("securityKind"); if (security instanceof String) { securityKind = Boolean.parseBoolean((String) security); }else { securityKind = (boolean) security; } List<String> languages = new ArrayList<>(); // Using families if (StringUtils.isEmpty(domain) && StringUtils.isEmpty(language) && families != null && !families.isEmpty()) { for (String family : families) { String lang = findLanguage(family); if (!StringUtils.isEmpty(lang)) { languages.add(lang); } } } // Using domain else if (!StringUtils.isEmpty(domain) && StringUtils.isEmpty(language) && (families == null || families.isEmpty())) { String lang = findLanguage(domain); if (!StringUtils.isEmpty(lang)) { languages.add(lang); } } // Using language else if (!StringUtils.isEmpty(language) && StringUtils.isEmpty(domain) && (families == null || families.isEmpty())) { String lang = findLanguage(language); if (!StringUtils.isEmpty(lang)) { languages.add(lang); } } for(String lang : languages) { InternalRule rule = new InternalRule( key, name, checkerName, getSeverity(impact), subcategory, getDescription(subcategoryLongDescription), getRuleType(qualityKind, securityKind), lang); addLanguageTag(rule); addRuleTypeTag(rule, qualityKind, securityKind); putRuleIntoMap(lang, rule); } } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } } public static void generateRulesForFindBugCheckers(File jsonFile) throws Exception { JSONParser parser = new JSONParser(); try { Object obj = parser.parse(new InputStreamReader(new FileInputStream(jsonFile.getAbsolutePath()), StandardCharsets.UTF_8)); JSONObject root = (JSONObject) obj; JSONArray issues = (JSONArray) root.get("issue_type"); Iterator<JSONObject> iterator = issues.iterator(); while( iterator.hasNext() ) { JSONObject childJSON = (JSONObject) iterator.next(); String checkerName = (String) childJSON.get("type"); String subcategory = (String) childJSON.get("subtype"); JSONObject name = (JSONObject) childJSON.get("name"); String ruleName = (String) name.get("en"); JSONObject desc = (JSONObject) childJSON.get("description"); String description = (String) desc.get("en"); JSONObject properties = (JSONObject) childJSON.get("cim_checker_properties"); String impact = (String) properties.get("impact"); boolean qualityKind = (boolean) properties.get("qualityKind"); boolean securityKind = (boolean) properties.get("securityKind"); String key = checkerName + "_" + subcategory; InternalRule rule = new InternalRule( key, ruleName, checkerName, getSeverity(impact), subcategory, getDescription(description), getRuleType(qualityKind, securityKind), JAVA_LANGUAGE); addAdditionalTag(rule, "findbugs"); addLanguageTag(rule); addRuleTypeTag(rule, qualityKind, securityKind); putRuleIntoMap(JAVA_LANGUAGE, rule); } } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } } /** * Write the result of the rules generation to one xml file per language. This is the step that actually updates the * resources used by the plugin. */ public static void writeRulesToFiles(File xmlDir){ /** * Print out rules for each language. */ for(String language : rulesList.keySet()){ File xmlFile = new File(xmlDir, "coverity-" + language + ".xml"); PrintWriter xmlFileOut = null; try { xmlFileOut = new PrintWriter(xmlFile,"UTF-8" ); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } xmlFileOut.println("<rules>"); String domain = null; if (language.equals(JAVA_LANGUAGE)) { domain = "STATIC_JAVA"; } else if (language.equals(CPP_LANGUAGE) || language.equals(OBJECTIVE_C)) { domain = "STATIC_C"; } else if (language.equals(CS_LANGUAGE)) { domain = "STATIC_CS"; } else if (language.equals(JAVASCRIPT_LANGUAGE) || language.equals(PYTHON_LANGUAGE) || language.equals(PHP_LANGUAGE)) { domain = "OTHER"; } for (String key : rulesList.get(language).keySet()) { for (InternalRule rule : rulesList.get(language).get(key)) { xmlFileOut.println(" <rule>"); xmlFileOut.println(" <key>" + StringEscapeUtils.escapeXml(domain + "_" + rule.getKey()) + "</key>"); xmlFileOut.println(" <name>" + StringEscapeUtils.escapeXml(rule.getRuleName()) + "</name>"); xmlFileOut.println(" <internalKey>" + StringEscapeUtils.escapeXml(domain + "_" + rule.getKey()) + "</internalKey>"); xmlFileOut.println(" <description>" + StringEscapeUtils.escapeXml(rule.getDescription()) + "</description>"); xmlFileOut.println(" <severity>" + StringEscapeUtils.escapeXml(rule.getSeverity()) + "</severity>"); xmlFileOut.println(" <cardinality>SINGLE</cardinality>"); xmlFileOut.println(" <status>READY</status>"); xmlFileOut.println(" <type>" + StringEscapeUtils.escapeXml(rule.getRuleType()) + "</type>"); for (String tag : rule.getTags()) { xmlFileOut.println(" <tag>" + StringEscapeUtils.escapeXml(tag) + "</tag>"); } xmlFileOut.println(" </rule>"); } } xmlFileOut.println("</rules>"); xmlFileOut.close(); System.out.println("The following file has been updated: " + xmlFile.getPath()); } } public static String findLanguage(String lang) { if (lang.equals("Java") || lang.equals("STATIC_JAVA")) { return JAVA_LANGUAGE; } else if (lang.equals("C/C++")){ return CPP_LANGUAGE; } else if (lang.equals("C#")) { return CS_LANGUAGE; } else if (lang.equals("JavaScript")) { return JAVASCRIPT_LANGUAGE; } else if (lang.equals("Python")) { return PYTHON_LANGUAGE; } else if (lang.equals("PHP")) { return PHP_LANGUAGE; } else if (lang.equals("Objective-C/C++")) { return OBJECTIVE_C; } return StringUtils.EMPTY; } public static String getDescription(String description) { String linkRegex = "\\(<a href=\"([^\"]*?)\" target=\"_blank\">(.*?)</a>\\)"; String codeRegex = "<code>(.*?)</code>"; description = description.replaceAll(linkRegex, ""); description = description.replaceAll(codeRegex, "$1"); description = description.trim(); return description; } public static String getSeverity(String impact) { String severity = "MAJOR"; if(impact.equals("High")){ severity = "BLOCKER"; } if(impact.equals("Medium")){ severity = "CRITICAL"; } return severity; } public static void putRuleIntoMap(String language, InternalRule rule) { String key = rule.getCheckerName(); String lang; if (language.equals(OBJECTIVE_C)) { lang = CPP_LANGUAGE; } else { lang = language; } if (!rulesList.containsKey(lang)) { rulesList.put(lang, new HashMap<String, List<InternalRule>>()); } if (!rulesList.get(lang).containsKey(key)) { List<InternalRule> list = new ArrayList<InternalRule>(); list.add(rule); rulesList.get(lang).put(key, list); } else { if (!rulesList.get(lang).get(key).contains(rule)) { rulesList.get(lang).get(key).add(rule); } else { for(InternalRule rule1 : rulesList.get(lang).get(key)) { if (rule1.equals(rule)) { for (String tag: rule.getTags()) { if (!rule1.getTags().contains(tag)) { rule1.getTags().add(tag); } } } } } } } public static String getRuleType(boolean qualityKind, boolean securityKind) { if (!qualityKind & securityKind) { return VULNERABILITY; } else { return BUG; } } public static void addNoneSubcategory() { Map<String, List<InternalRule>> missingList = new HashMap<String, List<InternalRule>>(); // Find rule without "none" subcategory for (String language : rulesList.keySet()) { for (String key: rulesList.get(language).keySet()) { boolean isNoneIncluded = false; InternalRule rule = null; for (InternalRule currentRule : rulesList.get(language).get(key)) { if (currentRule.getSubcategory().equals("none")) { isNoneIncluded = true; } rule = currentRule; } if (!isNoneIncluded) { if (!missingList.containsKey(language)) { missingList.put(language, new ArrayList<>()); } missingList.get(language).add(rule); } } } // Add rules with "none" subcategory for (Map.Entry<String, List<InternalRule>> entry : missingList.entrySet()) { for (InternalRule rule : entry.getValue()) { InternalRule newRule = new InternalRule( rule.getCheckerName() + "_none", rule.getRuleName(), rule.getCheckerName(), rule.getSeverity(), "none", rule.getDescription(), BUG, entry.getKey()); addLanguageTag(newRule); addRuleTypeTag(newRule, true, false); rulesList.get(entry.getKey()).get(newRule.getCheckerName()).add(newRule); } } } public static void addFallbackRuleForLanguage() { for (String language : rulesList.keySet()) { InternalRule newRule = new InternalRule( "coverity-" + language, "Coverity General " + StringUtils.upperCase(language), "coverity-" + language, "MAJOR", "none", "Coverity General " + StringUtils.upperCase(language), BUG, language ); List<InternalRule> list = new ArrayList<InternalRule>(); addLanguageTag(newRule); addRuleTypeTag(newRule, true, false); list.add(newRule); rulesList.get(language).put(newRule.getCheckerName(), list); } } public static void addDifferentOriginRules() { List<InternalRule> rules = new ArrayList<>(); InternalRule misraRule = new InternalRule( "MISRA.*", "Coverity MISRA : Coding Standard Violation", "MISRA.*", "MAJOR", "none", "Coverity MISRA : Coding Standard Violation", BUG, CPP_LANGUAGE ); addAdditionalTag(misraRule, "misra"); rules.add(misraRule); InternalRule pwRule = new InternalRule( "PW.*", "Coverity PW : Parse Warnings", "PW.*", "MAJOR", "none", "Coverity PW : Parse Warnings", BUG, CPP_LANGUAGE ); addAdditionalTag(pwRule, "parse-warning"); rules.add(pwRule); InternalRule swRule = new InternalRule( "SW.*", "Coverity SW : Semantic Warnings", "SW.*", "MAJOR", "none", "Coverity SW : Semantic Warnings", BUG, CPP_LANGUAGE ); addAdditionalTag(swRule, "semantic-warning"); rules.add(swRule); InternalRule rwRule = new InternalRule( "RW.*", "Coverity RW : Recovery Warnings", "RW.*", "MAJOR", "none", "Coverity RW : Recovery Warnings", BUG, CPP_LANGUAGE ); addAdditionalTag(rwRule, "recovery-warning"); rules.add(rwRule); InternalRule msvscaRule = new InternalRule( "MSVSCA.*", "Coverity MSVSCA : Microsoft Visual Studio Code Analysis", "MSVSCA.*", "MAJOR", "none", "Coverity MSVSCA : Microsoft Visual Studio Code Analysis", BUG, CS_LANGUAGE ); addAdditionalTag(msvscaRule, "msvsca"); rules.add(msvscaRule); InternalRule jshintRule = new InternalRule( "JSHINT.*", "Coverity JSHINT : JSHint Warning", "JSHINT.*", "MAJOR", "none", "Coverity JSHINT : JSHint Warning", BUG, JAVASCRIPT_LANGUAGE ); addAdditionalTag(jshintRule, "jshint"); rules.add(jshintRule); for (InternalRule rule : rules) { List<InternalRule> tempList = new ArrayList<InternalRule>(); addLanguageTag(rule); addRuleTypeTag(rule, true, false); tempList.add(rule); if (rule.getKey().equals("MSVSCA.*")) { rulesList.get(CS_LANGUAGE).put(rule.getCheckerName(), tempList); } else if (rule.getKey().equals("JSHINT.*")) { rulesList.get(JAVASCRIPT_LANGUAGE).put(rule.getCheckerName(), tempList); } else { rulesList.get(CPP_LANGUAGE).put(rule.getCheckerName(), tempList); } } } public static void setOutputFilePath(String filePath) { outputFilePath = filePath; } public static void addAdditionalTag(InternalRule rule, String tag) { if (rule != null && !StringUtils.isEmpty(tag)) { rule.getTags().add(tag); } } public static void addLanguageTag(InternalRule rule) { if (rule.getLanguage().equals(JAVA_LANGUAGE)) { rule.getTags().add("java"); } else if (rule.getLanguage().equals(CPP_LANGUAGE)) { rule.getTags().add("c++"); rule.getTags().add("c"); } else if (rule.getLanguage().equals(CS_LANGUAGE)) { rule.getTags().add("c#"); } else if (rule.getLanguage().equals(JAVASCRIPT_LANGUAGE)) { rule.getTags().add("js"); } else if (rule.getLanguage().equals(PYTHON_LANGUAGE)) { rule.getTags().add("python"); } else if (rule.getLanguage().equals(PHP_LANGUAGE)) { rule.getTags().add("php"); } else if (rule.getLanguage().equals(OBJECTIVE_C)) { rule.getTags().add("objective-c"); } } public static void addRuleTypeTag(InternalRule rule, boolean qualityKind, boolean securityKind) { if (qualityKind) { rule.getTags().add("coverity-quality"); } if (securityKind) { rule.getTags().add("coverity-security"); } } }