diff --git a/sonar-keyware-plugins-cxx/pom.xml b/sonar-keyware-plugins-cxx/pom.xml index e22ac5c..4c2bf5f 100644 --- a/sonar-keyware-plugins-cxx/pom.xml +++ b/sonar-keyware-plugins-cxx/pom.xml @@ -18,7 +18,7 @@ target/${project.artifactId}-${project.version}.jar - com.keyware.sonar.cxx.CxxSecurityDesignPlugin + com.keyware.sonar.cxx.CxxPlugin C++ 信息安全性设计准则 ${basedir}/../${aggregate.report.dir} @@ -30,7 +30,7 @@ 9.9.0.65466 9.14.0.375 - 8.9 + 8.9 2.10.1 33.0.0-jre 3.0.2 @@ -51,12 +51,6 @@ sonar-plugin-api-impl test - - org.sonarsource.sonarqube-plugins.cxx - sonar-cxx-plugin - ${sonar-cxx.versin} - provided - org.sonarsource.sonarqube-plugins.cxx cxx-squid @@ -123,15 +117,8 @@ org.sonarsource.sonar-packaging-maven-plugin sonar-packaging-maven-plugin - true - keywareCxxPlugin - C++ 安全性设计准则 - com.keyware.sonar.cxx.CxxSecurityDesignRulesPlugin - true - true - 9.14.0.375 - java:${project.version} + ${pluginApiMinVersion} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CustomCxxRulesDefinition.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CustomCxxRulesDefinition.java new file mode 100644 index 0000000..8f27918 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CustomCxxRulesDefinition.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.sonar.api.resources.Language; +import org.sonar.api.scanner.ScannerSide; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.cxx.squidbridge.annotations.AnnotationBasedRulesDefinition; + +import java.util.Arrays; + +/** + * + * @author jocs + */ +@ScannerSide +public abstract class CustomCxxRulesDefinition implements RulesDefinition { + + @Override + public void define(Context context) { + var repo = context.createRepository(repositoryKey(), getLanguage().getKey()) + .setName(repositoryName()); + + // Load metadata from check classes' annotations + new AnnotationBasedRulesDefinition(repo, getLanguage().getKey()).addRuleClasses(false, + Arrays.asList(checkClasses())); + + repo.done(); + } + + /** + * Name of the custom rule repository. + * + * @return + */ + public abstract Language getLanguage(); + + /** + * Name of the custom rule repository. + * + * @return + */ + public abstract String repositoryName(); + + /** + * Key of the custom rule repository. + * + * @return + */ + public abstract String repositoryKey(); + + /** + * Array of the custom rules classes. + */ + @SuppressWarnings("rawtypes") + public abstract Class[] checkClasses(); + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxChecks.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxChecks.java new file mode 100644 index 0000000..8539668 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxChecks.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.sonar.cxx.sslr.api.Grammar; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.rule.Checks; +import org.sonar.api.rule.RuleKey; +import org.sonar.cxx.squidbridge.SquidAstVisitor; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import java.util.*; + +public final class CxxChecks { + + private final CheckFactory checkFactory; + private final Set>> checksByRepository = new HashSet<>(); + + private CxxChecks(CheckFactory checkFactory) { + this.checkFactory = checkFactory; + } + + public static CxxChecks createCxxCheck(CheckFactory checkFactory) { + return new CxxChecks(checkFactory); + } + + @SuppressWarnings("rawtypes") + public CxxChecks addChecks(String repositoryKey, Iterable checkClass) { + checksByRepository.add(checkFactory + .>create(repositoryKey) + .addAnnotatedChecks(checkClass)); + + return this; + } + + public CxxChecks addCustomChecks(@Nullable CustomCxxRulesDefinition[] customRulesDefinitions) { + if (customRulesDefinitions != null) { + for (var rulesDefinition : customRulesDefinitions) { + addChecks(rulesDefinition.repositoryKey(), new ArrayList<>(Arrays.asList(rulesDefinition.checkClasses()))); + } + } + + return this; + } + + public List> all() { + var allVisitors = new ArrayList>(); + + for (var checks : checksByRepository) { + allVisitors.addAll(checks.all()); + } + + return allVisitors; + } + + @CheckForNull + public RuleKey ruleKey(SquidAstVisitor check) { + for (var checks : checksByRepository) { + RuleKey ruleKey = checks.ruleKey(check); + if (ruleKey != null) { + return ruleKey; + } + } + return null; + } + + public Set>> getChecks() { + return new HashSet<>(checksByRepository); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxLanguage.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxLanguage.java new file mode 100644 index 0000000..869d791 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxLanguage.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import org.sonar.api.config.Configuration; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.resources.AbstractLanguage; +import org.sonar.api.resources.Qualifiers; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +/** + * {@inheritDoc} + */ +public class CxxLanguage extends AbstractLanguage { + + /** + * cxx language key + */ + public static final String KEY = "cxx"; + + /** + * cxx language name + */ + public static final String NAME = "CXX"; + + /** + * Key of the file suffix parameter + */ + public static final String FILE_SUFFIXES_KEY = "sonar.cxx.file.suffixes"; + + /** + * Default cxx files knows suffixes + */ + public static final String DEFAULT_FILE_SUFFIXES = "-"; + + /** + * Settings of the plugin. + */ + private final Configuration config; + + public CxxLanguage(Configuration config) { + super(KEY, NAME); + this.config = config; + } + + public static List properties() { + return Collections.unmodifiableList(Arrays.asList( + PropertyDefinition.builder(FILE_SUFFIXES_KEY) + .defaultValue(DEFAULT_FILE_SUFFIXES) + .name("File suffixes") + .multiValues(true) + .description( + "List of suffixes for files to analyze (e.g. `.cxx,.cpp,.cc,.c,.hxx,.hpp,.hh,.h`)." + + " In the SonarQube UI, enter one file suffixe per field." + + " To turn off the CXX language, set the first entry to `-`." + ) + .category("CXX") + .subCategory("(1) General") + .onQualifiers(Qualifiers.PROJECT) + .build() + )); + } + + /** + * {@inheritDoc} + * + * @see AbstractLanguage#getFileSuffixes() + */ + @Override + public String[] getFileSuffixes() { + String[] suffixes = Arrays.stream(config.getStringArray(FILE_SUFFIXES_KEY)) + .filter(s -> s != null && !s.trim().isEmpty()).toArray(String[]::new); + if (suffixes.length == 0) { + suffixes = Iterables.toArray(Splitter.on(',').split(DEFAULT_FILE_SUFFIXES), String.class); + } + if ("-".equals(suffixes[0])) { + suffixes = new String[]{"disabled"}; + } + return suffixes; + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSecurityDesignRulesPlugin.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxMetricDefinition.java similarity index 52% rename from sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSecurityDesignRulesPlugin.java rename to sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxMetricDefinition.java index 6184a52..a399454 100644 --- a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSecurityDesignRulesPlugin.java +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxMetricDefinition.java @@ -6,20 +6,19 @@ */ package com.keyware.sonar.cxx; -import com.keyware.sonar.cxx.rules.CxxSecurityDesignRulesRepository; -import org.sonar.api.Plugin; -import org.sonar.plugins.cxx.CxxLanguage; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; +import org.sonar.cxx.CxxMetrics; -/** - * TODO CxxSecurityDesignRulesPlugin - * - * @author GuoXin - * @date 2024/1/6 - */ -public class CxxSecurityDesignRulesPlugin implements Plugin { - @Override - public void define(Context context) { - context.addExtension(CxxLanguage.class); - context.addExtension(CxxSecurityDesignRulesRepository.class); - } +import java.util.Collections; +import java.util.List; + +public class CxxMetricDefinition implements Metrics { + + @Override + public List getMetrics() { + return Collections.unmodifiableList( + CxxMetrics.getMetrics() + ); + } } diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxPlugin.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxPlugin.java new file mode 100644 index 0000000..442ec20 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxPlugin.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.keyware.sonar.cxx.rules.CxxSecurityDesignRulesRepository; +import org.sonar.api.Plugin; +import org.sonar.cxx.AggregateMeasureComputer; +import org.sonar.cxx.DensityMeasureComputer; +import org.sonar.cxx.postjobs.FinalReport; +import org.sonar.cxx.prejobs.XlstSensor; +import org.sonar.cxx.sensors.clangsa.CxxClangSARuleRepository; +import org.sonar.cxx.sensors.clangsa.CxxClangSASensor; +import org.sonar.cxx.sensors.clangtidy.CxxClangTidyRuleRepository; +import org.sonar.cxx.sensors.clangtidy.CxxClangTidySensor; +import org.sonar.cxx.sensors.compiler.gcc.CxxCompilerGccRuleRepository; +import org.sonar.cxx.sensors.compiler.gcc.CxxCompilerGccSensor; +import org.sonar.cxx.sensors.compiler.vc.CxxCompilerVcRuleRepository; +import org.sonar.cxx.sensors.compiler.vc.CxxCompilerVcSensor; +import org.sonar.cxx.sensors.coverage.bullseye.CxxCoverageBullseyeSensor; +import org.sonar.cxx.sensors.coverage.cobertura.CxxCoverageCoberturaSensor; +import org.sonar.cxx.sensors.coverage.ctc.CxxCoverageTestwellCtcTxtSensor; +import org.sonar.cxx.sensors.coverage.vs.CxxCoverageVisualStudioSensor; +import org.sonar.cxx.sensors.cppcheck.CxxCppCheckRuleRepository; +import org.sonar.cxx.sensors.cppcheck.CxxCppCheckSensor; +import org.sonar.cxx.sensors.drmemory.CxxDrMemoryRuleRepository; +import org.sonar.cxx.sensors.drmemory.CxxDrMemorySensor; +import org.sonar.cxx.sensors.infer.CxxInferRuleRepository; +import org.sonar.cxx.sensors.infer.CxxInferSensor; +import org.sonar.cxx.sensors.other.CxxOtherRepository; +import org.sonar.cxx.sensors.other.CxxOtherSensor; +import org.sonar.cxx.sensors.pclint.CxxPCLintRuleRepository; +import org.sonar.cxx.sensors.pclint.CxxPCLintSensor; +import org.sonar.cxx.sensors.rats.CxxRatsRuleRepository; +import org.sonar.cxx.sensors.rats.CxxRatsSensor; +import org.sonar.cxx.sensors.tests.dotnet.CxxUnitTestResultsAggregator; +import org.sonar.cxx.sensors.tests.dotnet.CxxUnitTestResultsImportSensor; +import org.sonar.cxx.sensors.tests.xunit.CxxXunitSensor; +import org.sonar.cxx.sensors.utils.RulesDefinitionXmlLoader; +import org.sonar.cxx.sensors.valgrind.CxxValgrindRuleRepository; +import org.sonar.cxx.sensors.valgrind.CxxValgrindSensor; +import org.sonar.cxx.sensors.veraxx.CxxVeraxxRuleRepository; +import org.sonar.cxx.sensors.veraxx.CxxVeraxxSensor; + +import java.util.ArrayList; +import java.util.List; + +/** + * {@inheritDoc} + */ +public final class CxxPlugin implements Plugin { + + /** + * {@inheritDoc} + */ + @Override + public void define(Context context) { + var l = new ArrayList(); + + // plugin elements + l.add(CxxLanguage.class); + l.add(CxxSonarWayProfile.class); + l.add(CxxRuleRepository.class); + l.add(CxxSecurityDesignRulesRepository.class); + + // reusable elements + l.addAll(getSensorsImpl()); + + // properties elements + l.addAll(CxxLanguage.properties()); + l.addAll(CxxSquidSensor.properties()); + l.addAll(CxxCppCheckSensor.properties()); + l.addAll(CxxValgrindSensor.properties()); + l.addAll(CxxDrMemorySensor.properties()); + l.addAll(CxxPCLintSensor.properties()); + l.addAll(CxxRatsSensor.properties()); + l.addAll(CxxVeraxxSensor.properties()); + l.addAll(CxxOtherSensor.properties()); + l.addAll(CxxClangTidySensor.properties()); + l.addAll(CxxClangSASensor.properties()); + l.addAll(CxxCoverageBullseyeSensor.properties()); + l.addAll(CxxCoverageCoberturaSensor.properties()); + l.addAll(CxxCoverageTestwellCtcTxtSensor.properties()); + l.addAll(CxxCoverageVisualStudioSensor.properties()); + l.addAll(CxxXunitSensor.properties()); + l.addAll(CxxUnitTestResultsImportSensor.properties()); + l.addAll(CxxCompilerVcSensor.properties()); + l.addAll(CxxCompilerGccSensor.properties()); + + context.addExtensions(l); + } + + static private List getSensorsImpl() { + var l = new ArrayList(); + + // utility classes + l.add(CxxUnitTestResultsAggregator.class); + l.add(RulesDefinitionXmlLoader.class); + + // metrics + l.add(CxxMetricDefinition.class); + // ComputeEngine: propagate metrics through all levels (FILE -> MODULE -> PROJECT) + l.add(AggregateMeasureComputer.class); + // ComputeEngine: calculate new metrics from existing ones + l.add(DensityMeasureComputer.class); + + // pre jobs + l.add(DroppedPropertiesSensor.class); + l.add(XlstSensor.class); + + // issue sensors + l.add(CxxSquidSensor.class); + l.add(CxxRatsSensor.class); + l.add(CxxCppCheckSensor.class); + l.add(CxxInferSensor.class); + l.add(CxxPCLintSensor.class); + l.add(CxxDrMemorySensor.class); + l.add(CxxCompilerGccSensor.class); + l.add(CxxCompilerVcSensor.class); + l.add(CxxVeraxxSensor.class); + l.add(CxxValgrindSensor.class); + l.add(CxxClangTidySensor.class); + l.add(CxxClangSASensor.class); + l.add(CxxOtherSensor.class); + + // test sensors + l.add(CxxXunitSensor.class); + l.add(CxxUnitTestResultsImportSensor.class); + + // coverage sensors + l.add(CxxCoverageBullseyeSensor.class); + l.add(CxxCoverageCoberturaSensor.class); + l.add(CxxCoverageTestwellCtcTxtSensor.class); + l.add(CxxCoverageVisualStudioSensor.class); + + // rule provides + l.add(CxxRatsRuleRepository.class); + l.add(CxxCppCheckRuleRepository.class); + l.add(CxxInferRuleRepository.class); + l.add(CxxPCLintRuleRepository.class); + l.add(CxxDrMemoryRuleRepository.class); + l.add(CxxCompilerVcRuleRepository.class); + l.add(CxxCompilerGccRuleRepository.class); + l.add(CxxVeraxxRuleRepository.class); + l.add(CxxValgrindRuleRepository.class); + l.add(CxxOtherRepository.class); + l.add(CxxClangTidyRuleRepository.class); + l.add(CxxClangSARuleRepository.class); + + // post jobs + l.add(FinalReport.class); + + return l; + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxRuleRepository.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxRuleRepository.java new file mode 100644 index 0000000..d8f4b5c --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxRuleRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.cxx.checks.CheckList; +import org.sonar.cxx.squidbridge.annotations.AnnotationBasedRulesDefinition; + +public class CxxRuleRepository implements RulesDefinition { + + private static final String REPOSITORY_NAME = "SonarQube"; + + @Override + public void define(Context context) { + var repository = context.createRepository("cxx", CxxLanguage.KEY). + setName(REPOSITORY_NAME); + new AnnotationBasedRulesDefinition(repository, CxxLanguage.KEY).addRuleClasses(false, CheckList.getChecks()); + repository.done(); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSonarWayProfile.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSonarWayProfile.java new file mode 100644 index 0000000..ce04ca4 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSonarWayProfile.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.google.common.io.Resources; +import com.google.gson.Gson; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; +import org.sonar.cxx.checks.CheckList; +import org.sonarsource.api.sonarlint.SonarLintSide; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.List; + +/** + * define built-in profile + */ +@SonarLintSide +public class CxxSonarWayProfile implements BuiltInQualityProfilesDefinition { + + private static String readResource(URL resource) { + try { + return Resources.toString(resource, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalStateException("Failed to read: " + resource, e); + } + } + + static Profile readProfile() { + URL resource = CxxSonarWayProfile.class.getResource("/org/sonar/l10n/cxx/rules/cxx/Sonar_way_profile.json"); + return new Gson().fromJson(readResource(resource), Profile.class); + } + + @Override + public void define(Context context) { + var sonarWay = context.createBuiltInQualityProfile("Sonar way", CxxLanguage.KEY); + Profile jsonProfile = readProfile(); + jsonProfile.ruleKeys.forEach((key) -> { + sonarWay.activateRule(CheckList.REPOSITORY_KEY, key); + }); + + sonarWay.done(); + } + + static class Profile { + + public String name; + public List ruleKeys; + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSquidSensor.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSquidSensor.java new file mode 100644 index 0000000..1660807 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/CxxSquidSensor.java @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.sonar.cxx.sslr.api.Grammar; +import org.sonar.api.PropertyType; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.TextRange; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.batch.sensor.cpd.NewCpdTokens; +import org.sonar.api.batch.sensor.highlighting.NewHighlighting; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.batch.sensor.issue.NewIssueLocation; +import org.sonar.api.config.PropertyDefinition; +import org.sonar.api.issue.NoSonarFilter; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.api.measures.Metric; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.scanner.sensor.ProjectSensor; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.cxx.CxxAstScanner; +import org.sonar.cxx.CxxMetrics; +import org.sonar.cxx.api.CxxMetric; +import org.sonar.cxx.checks.CheckList; +import org.sonar.cxx.config.CxxSquidConfiguration; +import org.sonar.cxx.config.MsBuild; +import org.sonar.cxx.sensors.utils.CxxUtils; +import org.sonar.cxx.squidbridge.SquidAstVisitor; +import org.sonar.cxx.squidbridge.api.SourceCode; +import org.sonar.cxx.squidbridge.api.SourceFile; +import org.sonar.cxx.squidbridge.indexer.QueryByType; +import org.sonar.cxx.visitors.CxxCpdVisitor; +import org.sonar.cxx.visitors.CxxHighlighterVisitor; +import org.sonar.cxx.visitors.CxxPublicApiVisitor; +import org.sonar.cxx.visitors.MultiLocatitionSquidCheck; + +import javax.annotation.Nullable; +import java.io.File; +import java.io.Serializable; +import java.nio.file.Path; +import java.util.*; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +/** + * {@inheritDoc} + */ +public class CxxSquidSensor implements ProjectSensor { + + public static final String SQUID_DISABLED_KEY = "sonar.cxx.squid.disabled"; + public static final String DEFINES_KEY = "sonar.cxx.defines"; + public static final String INCLUDE_DIRECTORIES_KEY = "sonar.cxx.includeDirectories"; + public static final String ERROR_RECOVERY_KEY = "sonar.cxx.errorRecoveryEnabled"; + public static final String FORCE_INCLUDES_KEY = "sonar.cxx.forceIncludes"; + public static final String JSON_COMPILATION_DATABASE_KEY = "sonar.cxx.jsonCompilationDatabase"; + public static final String JSON_COMPILATION_DATABASE_ONLY_CONTAINED_FILES_KEY + = "sonar.cxx.jsonCompilationDatabase.analyzeOnlyContainedFiles"; + + public static final String FUNCTION_COMPLEXITY_THRESHOLD_KEY = "sonar.cxx.metric.func.complexity.threshold"; + public static final String FUNCTION_SIZE_THRESHOLD_KEY = "sonar.cxx.metric.func.size.threshold"; + + public static final String CPD_IGNORE_LITERALS_KEY = "sonar.cxx.metric.cpd.ignoreLiterals"; + public static final String CPD_IGNORE_IDENTIFIERS_KEY = "sonar.cxx.metric.cpd.ignoreIdentifiers"; + + private static final Logger LOG = Loggers.get(CxxSquidSensor.class); + + private final FileLinesContextFactory fileLinesContextFactory; + private final CxxChecks checks; + private final NoSonarFilter noSonarFilter; + + private SensorContext context; + + /** + * {@inheritDoc} + */ + public CxxSquidSensor(FileLinesContextFactory fileLinesContextFactory, + CheckFactory checkFactory, + NoSonarFilter noSonarFilter) { + this(fileLinesContextFactory, checkFactory, noSonarFilter, null); + } + + /** + * {@inheritDoc} + */ + public CxxSquidSensor(FileLinesContextFactory fileLinesContextFactory, + CheckFactory checkFactory, + NoSonarFilter noSonarFilter, + @Nullable CustomCxxRulesDefinition[] customRulesDefinition) { + this.checks = CxxChecks.createCxxCheck(checkFactory) + .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks()) + .addCustomChecks(customRulesDefinition); + this.fileLinesContextFactory = fileLinesContextFactory; + this.noSonarFilter = noSonarFilter; + } + + public static List properties() { + return Collections.unmodifiableList(Arrays.asList( + PropertyDefinition.builder(INCLUDE_DIRECTORIES_KEY) + .multiValues(true) + .name("(2.2) Include Directories") + .description( + "Comma-separated list of directories where the preprocessor looks for include files." + + " The path may be either absolute or relative to the project base directory." + + " In the SonarQube UI, enter one entry per field." + ) + .category("CXX") + .subCategory("(2) Preprocessor") + .onQualifiers(Qualifiers.PROJECT) + .build(), + PropertyDefinition.builder(FORCE_INCLUDES_KEY) + .multiValues(true) + .category("CXX") + .subCategory("(2) Preprocessor") + .name("(2.3) Force Includes") + .description( + "Comma-separated list of include files implicitly inserted at the beginning of each source file." + + " This has the same effect as specifying the file with double quotation marks in an `#include` directive" + + " on the first line of every source file. If you add multiple files they are included in the order they" + + " are listed from left to right. The path may be either absolute or relative to the" + + " project base directory." + + " In the SonarQube UI, enter one entry per field." + ) + .onQualifiers(Qualifiers.PROJECT) + .build(), + PropertyDefinition.builder(SQUID_DISABLED_KEY) + .defaultValue(Boolean.FALSE.toString()) + .name("Disable Squid sensor") + .description( + "Disable parsing of source code, syntax hightligthing and metric generation." + + " The source files are still indexed, reports can be read and their results displayed." + + " Turning off will speed up reading of source files." + ) + .category("CXX") + .subCategory("(1) General") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(DEFINES_KEY) + .name("(2.1) Macros") + .description( + "List of macros to be used by the preprocessor during analysis. Enter one macro per line." + + " The syntax is the same as `#define` directives, except for the `#define` keyword itself." + ) + .category("CXX") + .subCategory("(2) Preprocessor") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.TEXT) + .build(), + PropertyDefinition.builder(ERROR_RECOVERY_KEY) + .defaultValue(Boolean.TRUE.toString()) + .name("Parse Error Recovery") + .description( + "Defines the mode for error handling of report files and parsing errors." + + " `False` (strict) terminates after an error or `True` (tolerant) continues." + ) + .category("CXX") + .subCategory("(1) General") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(MsBuild.REPORT_PATH_KEY) + .name("(2.6) Path(s) to MSBuild Log(s)") + .description( + "Read one ore more MSBuild .LOG files to automatically extract the required macros `sonar.cxx.defines`" + + " and include directories `sonar.cxx.includeDirectories`. The path may be either absolute or relative" + + " to the project base directory." + + " In the SonarQube UI, enter one entry per field." + ) + .category("CXX") + .subCategory("(2) Preprocessor") + .onQualifiers(Qualifiers.PROJECT) + .multiValues(true) + .build(), + PropertyDefinition.builder(MsBuild.REPORT_ENCODING_DEF) + .defaultValue(MsBuild.DEFAULT_ENCODING_DEF) + .name("(2.7) MSBuild Log Encoding") + .description( + "Defines the encoding to be used to read the files from `sonar.cxx.msbuild.reportPaths` (default is `UTF-8`)." + ) + .category("CXX") + .subCategory("(2) Preprocessor") + .onQualifiers(Qualifiers.PROJECT) + .build(), + PropertyDefinition.builder(JSON_COMPILATION_DATABASE_KEY) + .category("CXX") + .subCategory("(2) Preprocessor") + .name("(2.4) JSON Compilation Database") + .description( + "Read a JSON Compilation Database file to automatically extract the required macros `sonar.cxx.defines`" + + " and include directories `sonar.cxx.includeDirectories` from a file. The path may be either absolute" + + " or relative to the project base directory." + ) + .onQualifiers(Qualifiers.PROJECT) + .build(), + PropertyDefinition.builder(JSON_COMPILATION_DATABASE_ONLY_CONTAINED_FILES_KEY) + .defaultValue(Boolean.FALSE.toString()) + .category("CXX") + .subCategory("(2) Preprocessor") + .name("(2.5) JSON Compilation Database analyze only contained files") + .description( + "If 'analyzeOnlyContainedFiles=True' is used, the analyzed files will be limited to the files contained" + + " in the 'JSON Compilation Database' file - the intersection of the files configured via" + + " 'sonar.projectBaseDir' and the files contained in the 'JSON Compilation Database' file" + + " (default is False)." + ) + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(CxxPublicApiVisitor.API_FILE_SUFFIXES_KEY) + .defaultValue(CxxPublicApiVisitor.API_DEFAULT_FILE_SUFFIXES) + .name("Public API File suffixes") + .multiValues(true) + .description( + "Comma-separated list of suffixes for files to be searched for API comments and to create API metrics." + + " In the SonarQube UI, enter one entry per field." + ) + .category("CXX") + .subCategory("(3) Metrics") + .onQualifiers(Qualifiers.PROJECT) + .build(), + PropertyDefinition.builder(FUNCTION_COMPLEXITY_THRESHOLD_KEY) + .defaultValue("10") + .name("Complex Functions ...") + .description( + "The parameter defines the threshold for `Complex Functions ...`." + + " Functions and methods with a higher cyclomatic complexity are classified as `complex`." + ) + .category("CXX") + .subCategory("(3) Metrics") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.INTEGER) + .build(), + PropertyDefinition.builder(FUNCTION_SIZE_THRESHOLD_KEY) + .defaultValue("20") + .name("Big Functions ...") + .description( + "The parameter defines the threshold for `Big Functions ...`." + + " Functions and methods with more lines of code are classified as `big`." + ) + .category("CXX") + .subCategory("(3) Metrics") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.INTEGER) + .build(), + PropertyDefinition.builder(CPD_IGNORE_LITERALS_KEY) + .defaultValue(Boolean.FALSE.toString()) + .name("Ignores Literal Value Differences") + .description( + "Configure the metrics `Duplications` (Copy Paste Detection). `True` ignores literal" + + " (numbers, characters and strings) value differences when evaluating a duplicate block. This means" + + " that e.g. `foo=42;` and `foo=43;` will be seen as equivalent." + ) + .category("CXX") + .subCategory("(4) Duplications") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .build(), + PropertyDefinition.builder(CPD_IGNORE_IDENTIFIERS_KEY) + .defaultValue(Boolean.FALSE.toString()) + .name("Ignores Identifier Value Differences") + .description( + "Configure the metrics `Duplications` (Copy Paste Detection). `True` ignores identifier value differences" + + " when evaluating a duplicate block e.g. variable names, methods names, and so forth." + ) + .category("CXX") + .subCategory("(4) Duplications") + .onQualifiers(Qualifiers.PROJECT) + .type(PropertyType.BOOLEAN) + .build() + )); + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .name("CXX") + .onlyOnLanguage(CxxLanguage.KEY) + .onlyOnFileType(InputFile.Type.MAIN) + .onlyWhenConfiguration(conf -> !conf.getBoolean(SQUID_DISABLED_KEY).orElse(false)); + } + + /** + * {@inheritDoc} + */ + @Override + public void execute(SensorContext context) { + this.context = context; + + // add visitor only if corresponding rule is active + var visitors = new ArrayList>(); + for (var check : checks.all()) { + RuleKey key = checks.ruleKey(check); + if (key != null) { + if (context.activeRules().find(key) != null) { + visitors.add(check); + } + } + } + + var squidConfig = createConfiguration(); + var scanner = CxxAstScanner.create(squidConfig, visitors.toArray(new SquidAstVisitor[visitors.size()])); + + Iterable inputFiles = getInputFiles(context, squidConfig); + scanner.scanInputFiles(inputFiles); + + Collection squidSourceFiles = scanner.getIndex().search(new QueryByType(SourceFile.class)); + save(squidSourceFiles); + } + + @Override + public String toString() { + return getClass().getSimpleName(); + } + + private String[] stripValue(String key, String regex) { + Optional value = context.config().get(key); + if (value.isPresent()) { + var PATTERN = Pattern.compile(regex); + return PATTERN.split(value.get(), -1); + } + return new String[0]; + } + + private CxxSquidConfiguration createConfiguration() { + var squidConfig = new CxxSquidConfiguration(context.fileSystem().baseDir().getAbsolutePath(), + context.fileSystem().encoding()); + + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.ERROR_RECOVERY_ENABLED, + context.config().get(ERROR_RECOVERY_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.CPD_IGNORE_LITERALS, + context.config().get(CPD_IGNORE_LITERALS_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.CPD_IGNORE_IDENTIFIERS, + context.config().get(CPD_IGNORE_IDENTIFIERS_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.FUNCTION_COMPLEXITY_THRESHOLD, + context.config().get(FUNCTION_COMPLEXITY_THRESHOLD_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.FUNCTION_SIZE_THRESHOLD, + context.config().get(FUNCTION_SIZE_THRESHOLD_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.API_FILE_SUFFIXES, + context.config().getStringArray(CxxPublicApiVisitor.API_FILE_SUFFIXES_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.JSON_COMPILATION_DATABASE, + context.config().get(JSON_COMPILATION_DATABASE_KEY)); + + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.DEFINES, + stripValue(DEFINES_KEY, "\\R")); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.FORCE_INCLUDES, + context.config().getStringArray(FORCE_INCLUDES_KEY)); + squidConfig.add(CxxSquidConfiguration.SONAR_PROJECT_PROPERTIES, CxxSquidConfiguration.INCLUDE_DIRECTORIES, + context.config().getStringArray(INCLUDE_DIRECTORIES_KEY)); + + squidConfig.readJsonCompilationDb(); + + if (context.config().hasKey(MsBuild.REPORT_PATH_KEY)) { + List logFiles = CxxUtils.getFiles(context, MsBuild.REPORT_PATH_KEY); + squidConfig.readMsBuildFiles(logFiles, context.config().get(MsBuild.REPORT_ENCODING_DEF) + .orElse(MsBuild.DEFAULT_ENCODING_DEF)); + } + + return squidConfig; + } + + private Iterable getInputFiles(SensorContext context, CxxSquidConfiguration squidConfig) { + Iterable inputFiles = context.fileSystem().inputFiles( + context.fileSystem().predicates().and( + context.fileSystem().predicates().hasLanguage(CxxLanguage.KEY), + context.fileSystem().predicates().hasType(InputFile.Type.MAIN) + ) + ); + + if (context.config().hasKey(JSON_COMPILATION_DATABASE_KEY) + && context.config().getBoolean(JSON_COMPILATION_DATABASE_ONLY_CONTAINED_FILES_KEY).orElse(Boolean.FALSE)) { + // if the source of the configuration is JSON Compilation Database and analyzeOnlyContainedFiles=True, + // then analyze only the files contained in the db. + var inputFilesInConfig = squidConfig.getFiles(); + var result = StreamSupport.stream(inputFiles.spliterator(), false) + .filter(f -> inputFilesInConfig.contains(Path.of(f.uri()))) + .collect(Collectors.toList()); + inputFiles = result; + + LOG.info("Analyze only files contained in 'JSON Compilation Database': {} files", result.size()); + if (result.isEmpty()) { + LOG.error( + "No files are analyzed, check the settings of 'sonar.projectBaseDir' and 'sonar.cxx.jsonCompilationDatabase'." + ); + } + } + + return inputFiles; + } + + private void save(Collection sourceCodeFiles) { + for (var sourceCodeFile : sourceCodeFiles) { + try { + var sourceFile = (SourceFile) sourceCodeFile; + InputFile inputFile = context.fileSystem().inputFile( + context.fileSystem().predicates().hasPath(sourceFile.getKey()) + ); + saveMeasures(inputFile, sourceFile); + saveViolations(inputFile, sourceFile); + saveFileLinesContext(inputFile, sourceFile); + saveCpdTokens(inputFile, sourceFile); + saveHighlighting(inputFile, sourceFile); + } catch (IllegalStateException e) { + var msg = "Cannot save all measures for file '" + sourceCodeFile.getKey() + "'"; + CxxUtils.validateRecovery(msg, e, context.config()); + } + } + } + + private void saveMeasures(InputFile inputFile, SourceFile sourceFile) { + + // NOSONAR + noSonarFilter.noSonarInFile(inputFile, sourceFile.getNoSonarTagLines()); + + // CORE METRICS + saveMetric(inputFile, CoreMetrics.NCLOC, sourceFile.getInt(CxxMetric.LINES_OF_CODE)); + saveMetric(inputFile, CoreMetrics.STATEMENTS, sourceFile.getInt(CxxMetric.STATEMENTS)); + saveMetric(inputFile, CoreMetrics.FUNCTIONS, sourceFile.getInt(CxxMetric.FUNCTIONS)); + saveMetric(inputFile, CoreMetrics.CLASSES, sourceFile.getInt(CxxMetric.CLASSES)); + saveMetric(inputFile, CoreMetrics.COMPLEXITY, sourceFile.getInt(CxxMetric.COMPLEXITY)); + saveMetric(inputFile, CoreMetrics.COGNITIVE_COMPLEXITY, sourceFile.getInt(CxxMetric.COGNITIVE_COMPLEXITY)); + saveMetric(inputFile, CoreMetrics.COMMENT_LINES, sourceFile.getInt(CxxMetric.COMMENT_LINES)); + + // CUSTOM METRICS + // + // non-core metrics are not aggregated automatically, see AggregateMeasureComputer + // below metrics are calculated by means of DensityMeasureComputer + // + // 1. PUBLIC API + saveMetric(inputFile, CxxMetrics.PUBLIC_API, sourceFile.getInt(CxxMetric.PUBLIC_API)); + saveMetric(inputFile, CxxMetrics.PUBLIC_UNDOCUMENTED_API, sourceFile.getInt(CxxMetric.PUBLIC_UNDOCUMENTED_API)); + + // 2. FUNCTION COMPLEXITY + saveMetric(inputFile, CxxMetrics.COMPLEX_FUNCTIONS, sourceFile.getInt(CxxMetric.COMPLEX_FUNCTIONS)); + saveMetric(inputFile, CxxMetrics.COMPLEX_FUNCTIONS_LOC, sourceFile.getInt(CxxMetric.COMPLEX_FUNCTIONS_LOC)); + + // 3. FUNCTION SIZE + saveMetric(inputFile, CxxMetrics.LOC_IN_FUNCTIONS, sourceFile.getInt(CxxMetric.LOC_IN_FUNCTIONS)); + saveMetric(inputFile, CxxMetrics.BIG_FUNCTIONS, sourceFile.getInt(CxxMetric.BIG_FUNCTIONS)); + saveMetric(inputFile, CxxMetrics.BIG_FUNCTIONS_LOC, sourceFile.getInt(CxxMetric.BIG_FUNCTIONS_LOC)); + } + + private void saveViolations(InputFile inputFile, SourceFile sourceFile) { + if (sourceFile.hasCheckMessages()) { + for (var message : sourceFile.getCheckMessages()) { + var line = 1; + if (message.getLine() != null && message.getLine() > 0) { + line = message.getLine(); + } + + RuleKey ruleKey = checks.ruleKey((SquidAstVisitor) message.getCheck()); + if (ruleKey != null) { + var newIssue = context.newIssue().forRule(RuleKey.of(CheckList.REPOSITORY_KEY, ruleKey.rule())); + var location = newIssue.newLocation() + .on(inputFile) + .at(inputFile.selectLine(line)) + .message(message.getText(Locale.ENGLISH)); + + newIssue.at(location); + newIssue.save(); + } else { + LOG.debug("Unknown rule key: %s", message); + } + } + } + + if (MultiLocatitionSquidCheck.hasMultiLocationCheckMessages(sourceFile)) { + for (var issue : MultiLocatitionSquidCheck.getMultiLocationCheckMessages(sourceFile)) { + var newIssue = context.newIssue().forRule(RuleKey.of(CheckList.REPOSITORY_KEY, issue.getRuleId())); + var locationNr = 0; + for (var location : issue.getLocations()) { + final Integer line = Integer.valueOf(location.getLine()); + final NewIssueLocation newIssueLocation = newIssue.newLocation().on(inputFile).at(inputFile.selectLine(line)) + .message(location.getInfo()); + if (locationNr == 0) { + newIssue.at(newIssueLocation); + } else { + newIssue.addLocation(newIssueLocation); + } + ++locationNr; + } + newIssue.save(); + } + MultiLocatitionSquidCheck.eraseMultilineCheckMessages(sourceFile); + } + } + + private void saveFileLinesContext(InputFile inputFile, SourceFile sourceFile) { + // measures for the lines of file + var fileLinesContext = fileLinesContextFactory.createFor(inputFile); + List linesOfCode = (List) sourceFile.getData(CxxMetric.NCLOC_DATA); + linesOfCode.stream().sequential().distinct().forEach((line) -> { + try { + fileLinesContext.setIntValue(CoreMetrics.NCLOC_DATA_KEY, line, 1); + } catch (IllegalArgumentException | IllegalStateException e) { + // ignore errors: parsing errors could lead to wrong location data + LOG.debug("NCLOC error in file '{}' at line:{}", inputFile.filename(), line); + } + }); + List executableLines = (List) sourceFile.getData(CxxMetric.EXECUTABLE_LINES_DATA); + executableLines.stream().sequential().distinct().forEach((line) -> { + try { + fileLinesContext.setIntValue(CoreMetrics.EXECUTABLE_LINES_DATA_KEY, line, 1); + } catch (IllegalArgumentException | IllegalStateException e) { + // ignore errors: parsing errors could lead to wrong location data + LOG.debug("EXECUTABLE LINES error in file '{}' at line:{}", inputFile.filename(), line); + } + }); + fileLinesContext.save(); + } + + private void saveCpdTokens(InputFile inputFile, SourceFile sourceFile) { + NewCpdTokens cpdTokens = context.newCpdTokens().onFile(inputFile); + + List data = (List) sourceFile.getData(CxxMetric.CPD_TOKENS_DATA); + data.forEach((item) -> { + try { + TextRange range = inputFile.newRange(item.startLine, item.startCol, item.endLine, item.endCol); + cpdTokens.addToken(range, item.token); + } catch (IllegalArgumentException | IllegalStateException e) { + // ignore range errors: parsing errors could lead to wrong location data + LOG.debug("CPD error in file '{}' at line:{}, column:{}", inputFile.filename(), item.startLine, item.startCol); + } + }); + + cpdTokens.save(); + } + + private void saveHighlighting(InputFile inputFile, SourceFile sourceFile) { + NewHighlighting newHighlighting = context.newHighlighting().onFile(inputFile); + + List data = (List) sourceFile.getData( + CxxMetric.HIGHLIGTHING_DATA); + data.forEach((item) -> { + try { + newHighlighting.highlight(item.startLine, item.startLineOffset, item.endLine, item.endLineOffset, + TypeOfText.forCssClass(item.typeOfText)); + } catch (IllegalArgumentException | IllegalStateException e) { + // ignore highlight errors: parsing errors could lead to wrong location data + LOG.debug("Highlighting error in file '{}' at start:{}:{} end:{}:{}", inputFile.filename(), + item.startLine, item.startLineOffset, item.endLine, item.endLineOffset); + } + }); + + newHighlighting.save(); + } + + private void saveMetric(InputFile file, Metric metric, T value) { + context.newMeasure() + .withValue(value) + .forMetric(metric) + .on(file) + .save(); + } +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/DroppedPropertiesSensor.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/DroppedPropertiesSensor.java new file mode 100644 index 0000000..1bb7498 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/DroppedPropertiesSensor.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.sonar.api.batch.Phase; +import org.sonar.api.batch.sensor.SensorContext; +import org.sonar.api.batch.sensor.SensorDescriptor; +import org.sonar.api.notifications.AnalysisWarnings; +import org.sonar.api.scanner.sensor.ProjectSensor; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Phase(name = Phase.Name.PRE) +public class DroppedPropertiesSensor implements ProjectSensor { + + private static final Logger LOG = Loggers.get(DroppedPropertiesSensor.class); + + private static final String MSG_COMPILER = "Use 'sonar.cxx.vc' or 'sonar.cxx.gcc' instead." + + " Use 'sonar.cxx.msbuild' to read includes and defines from MSBuild log file."; + + private static final Map ALL_REMOVED_PROPERTIES = initRemovedProperties(); + private final AnalysisWarnings analysisWarnings; + + public DroppedPropertiesSensor(AnalysisWarnings analysisWarnings) { + this.analysisWarnings = analysisWarnings; + } + + private static Map initRemovedProperties() { + var map = new HashMap(); + map.put("sonar.cxx.include_directories", "Use 'sonar.cxx.includeDirectories' instead."); // V0.9.1 + map.put("sonar.cxx.externalrules.reportPaths", "Use 'sonar.cxx.other.reportPaths' instead."); // V0.9.1 + map.put("sonar.cxx.cppncss.reportPaths", ""); // V0.9.1 + map.put("sonar.cxx.other.sqales", ""); // V0.9.6 + map.put("sonar.cxx.xunit.provideDetails", ""); // V0.9.7 + map.put("sonar.cxx.coverage.itReportPaths", ""); // V0.9.8 + map.put("sonar.cxx.coverage.overallReportPaths", ""); // V0.9.8 + map.put("sonar.cxx.forceZeroCoverage", ""); // V0.9.8 + map.put("sonar.cxx.scanOnlySpecifiedSources", ""); // V1.0.0 + map.put("sonar.cxx.compiler.parser", MSG_COMPILER); // V1.2.0 + map.put("sonar.cxx.compiler.reportPaths", MSG_COMPILER); // V1.2.0 + map.put("sonar.cxx.compiler.regex", MSG_COMPILER); // V1.2.0 + map.put("sonar.cxx.compiler.charset", MSG_COMPILER); // V1.2.0 + map.put("sonar.cxx.missingIncludeWarnings", "Turn debug info on to get the information."); // V1.2.0 + map.put("sonar.cxx.cFilesPatterns", + "Define C++ keywords in an own header file and include it with 'sonar.cxx.forceIncludes' instead."); // V2.0.0 + map.put("sonar.cxx.suffixes.sources", "Use key 'sonar.cxx.file.suffixes' instead."); // V2.0.0 + map.put("sonar.cxx.suffixes.headers", + "Use key 'sonar.cxx.file.suffixes' instead. For API detection use 'sonar.cxx.metric.api.file.suffixes'."); // V2.0.0 + map.put("sonar.cxx.other.xslt.1.stylesheet", "Use 'sonar.cxx.xslt.1.stylesheet' instead."); // V2.0.0 + map.put("sonar.cxx.other.xslt.1.inputs", "Use 'sonar.cxx.xslt.1.inputs' instead."); // V2.0.0 + map.put("sonar.cxx.other.xslt.1.outputs", "Use 'sonar.cxx.xslt.1.outputs' instead."); // V2.0.0 + map.put("sonar.cxx.xunit.xsltURL", "Use 'sonar.cxx.xslt.xxx' instead."); // V2.0.0 + map.put("sonar.cxx.clangsa.reportPath", "Use 'sonar.cxx.clangsa.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.clangtidy.reportPath", "Use 'sonar.cxx.clangtidy.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.gcc.reportPath", "Use 'sonar.cxx.gcc.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.vc.reportPath", "Use 'sonar.cxx.vc.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.cppcheck.reportPath", "Use 'sonar.cxx.cppcheck.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.drmemory.reportPath", "Use 'sonar.cxx.drmemory.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.other.reportPath", "Use 'sonar.cxx.other.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.pclint.reportPath", "Use 'sonar.cxx.pclint.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.xunit.reportPath", "Use 'sonar.cxx.xunit.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.valgrind.reportPath", "Use 'sonar.cxx.valgrind.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.vera.reportPath", "Use 'sonar.cxx.vera.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.msbuild.reportPath", "Use 'sonar.cxx.msbuild.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.coverage.reportPath", "Use 'sonar.cxx.bullseye.reportPaths'" + + ", 'sonar.cxx.cobertura.reportPaths', 'sonar.cxx.vscoveragexml.reportPaths' or 'sonar.cxx.ctctxt.reportPaths'" + + " instead."); // V2.0.0 + map.put("sonar.cxx.funccomplexity.threshold", + "Use 'sonar.cxx.metric.func.complexity.threshold' instead."); // V2.0.0 + map.put("sonar.cxx.funcsize.threshold", "Use 'sonar.cxx.metric.func.size.threshold' instead."); // V2.0.0 + map.put("sonar.cxx.vstest.reportsPaths", "Use 'sonar.cxx.vstest.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.xunit.reportsPaths", "Use 'sonar.cxx.xunit.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.nunit.reportsPaths", "Use 'sonar.cxx.nunit.reportPaths' instead."); // V2.0.0 + map.put("sonar.cxx.clangtidy.charset", "Use 'sonar.cxx.clangtidy.encoding' instead."); // V2.0.0 + map.put("sonar.cxx.gcc.charset", "Use 'sonar.cxx.gcc.encoding' instead."); // V2.0.0 + map.put("sonar.cxx.vc.charset", "Use 'sonar.cxx.vc.encoding' instead."); // V2.0.0 + map.put("sonar.cxx.ctctxt.charset", "Use 'sonar.cxx.ctctxt.encoding' instead."); // V2.0.0 + map.put("sonar.cxx.msbuild.charset", "Use 'sonar.cxx.msbuild.encoding' instead."); // V2.0.0 + map.put("sonar.cxx.cpd.ignoreLiterals", "Use 'sonar.cxx.metric.cpd.ignoreLiterals' instead."); // V2.0.0 + map.put("sonar.cxx.cpd.ignoreIdentifiers", "Use 'sonar.cxx.metric.cpd.ignoreIdentifiers' instead."); // V2.0.0 + map.put("sonar.cxx.nunit.reportPaths", "If possible use 'sonar.cs.nunit.reportsPaths' instead."); // V2.0.0 + map.put("sonar.cxx.vstest.reportPaths", "If possible use 'sonar.cs.vstest.reportsPaths' instead."); // V2.0.0 + + return Collections.unmodifiableMap(map); + } + + @Override + public void describe(SensorDescriptor descriptor) { + descriptor + .onlyOnLanguage("cxx") + .onlyWhenConfiguration(configuration -> ALL_REMOVED_PROPERTIES.keySet().stream().anyMatch(configuration::hasKey)) + .name("CXX verify analysis parameters"); + } + + @Override + public void execute(SensorContext context) { + ALL_REMOVED_PROPERTIES.forEach((key, info) -> { + if (context.config().hasKey(key)) { + var msg = "CXX property '" + key + "' is no longer supported."; + if (!info.isEmpty()) { + msg += " " + info; + } + analysisWarnings.addUnique(msg); + LOG.warn(msg); + } + }); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/CxxSecurityDesignRulesRepository.java b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/CxxSecurityDesignRulesRepository.java index d1f4bbe..b007f8e 100644 --- a/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/CxxSecurityDesignRulesRepository.java +++ b/sonar-keyware-plugins-cxx/src/main/java/com/keyware/sonar/cxx/rules/CxxSecurityDesignRulesRepository.java @@ -6,6 +6,8 @@ */ package com.keyware.sonar.cxx.rules; +import com.keyware.sonar.cxx.CustomCxxRulesDefinition; +import com.keyware.sonar.cxx.CxxLanguage; import org.sonar.api.SonarEdition; import org.sonar.api.SonarProduct; import org.sonar.api.SonarQubeSide; @@ -13,8 +15,6 @@ import org.sonar.api.SonarRuntime; import org.sonar.api.resources.Language; import org.sonar.api.server.rule.RulesDefinition; import org.sonar.api.utils.Version; -import org.sonar.plugins.cxx.CustomCxxRulesDefinition; -import org.sonar.plugins.cxx.CxxLanguage; import java.util.Collections; import java.util.Objects; diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CustomCxxRulesDefinitionTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CustomCxxRulesDefinitionTest.java new file mode 100644 index 0000000..ad3101f --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CustomCxxRulesDefinitionTest.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.sonar.cxx.sslr.api.Grammar; +import org.junit.jupiter.api.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.resources.Language; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.check.Rule; +import org.sonar.check.RuleProperty; +import org.sonar.cxx.squidbridge.checks.SquidCheck; +import org.sonar.cxx.tag.Tag; + +import static org.assertj.core.api.Assertions.assertThat; + +class CustomCxxRulesDefinitionTest { + + private static final Language LANGUAGE = new CxxLanguage(new MapSettings().asConfig()); + private static final String REPOSITORY_NAME = "Custom Rule Repository"; + private static final String REPOSITORY_KEY = "CustomRuleRepository"; + + private static final String RULE_NAME = "This is my custom rule"; + private static final String RULE_KEY = "MyCustomRule"; + + @Test + void test() { + var rulesDefinition = new MyCustomPlSqlRulesDefinition(); + var context = new RulesDefinition.Context(); + rulesDefinition.define(context); + RulesDefinition.Repository repository = context.repository(REPOSITORY_KEY); + + assertThat(repository.name()).isEqualTo(REPOSITORY_NAME); + assertThat(repository.language()).isEqualTo(LANGUAGE.getKey()); + assertThat(repository.rules()).hasSize(1); + + RulesDefinition.Rule alertUseRule = repository.rule(RULE_KEY); + assertThat(alertUseRule).isNotNull(); + assertThat(alertUseRule.name()).isEqualTo(RULE_NAME); + + for (var rule : repository.rules()) { + for (var param : rule.params()) { + assertThat(param.description()).as("description for " + param.key()).isNotEmpty(); + } + } + } + + public static class MyCustomPlSqlRulesDefinition extends CustomCxxRulesDefinition { + + @Override + public String repositoryName() { + System.out.println(REPOSITORY_NAME); + return REPOSITORY_NAME; + } + + @Override + public String repositoryKey() { + return REPOSITORY_KEY; + } + + @SuppressWarnings("rawtypes") + @Override + public Class[] checkClasses() { + return new Class[]{MyCustomRule.class}; + } + + @Override + public Language getLanguage() { + return LANGUAGE; + } + } + + @Rule( + key = RULE_KEY, + name = RULE_NAME, + description = "desc", + tags = {Tag.BUG}) + public class MyCustomRule extends SquidCheck { + + @RuleProperty( + key = "customParam", + description = "Custom parameter", + defaultValue = "value") + public String customParam = "value"; + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxCheckListTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxCheckListTest.java new file mode 100644 index 0000000..98052fc --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxCheckListTest.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.sonar.cxx.checks.CheckList; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxCheckListTest { + + @Test + void count() { + assertThat(CheckList.getChecks()).hasSize(27); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxChecksTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxChecksTest.java new file mode 100644 index 0000000..656787b --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxChecksTest.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import com.sonar.cxx.sslr.api.Grammar; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.rule.internal.ActiveRulesBuilder; +import org.sonar.api.batch.rule.internal.NewActiveRule; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.resources.Language; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.server.rule.RulesDefinition; +import org.sonar.check.Rule; +import org.sonar.cxx.squidbridge.SquidAstVisitor; +import org.sonar.cxx.squidbridge.checks.SquidCheck; + +import javax.annotation.CheckForNull; +import java.util.ArrayList; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxChecksTest { + + private static final String DEFAULT_REPOSITORY_KEY = "DefaultRuleRepository"; + private static final String DEFAULT_RULE_KEY = "MyRule"; + private static final String CUSTOM_REPOSITORY_KEY = "CustomRuleRepository"; + private static final String CUSTOM_RULE_KEY = "MyCustomRule"; + + private MyCustomPlSqlRulesDefinition customRulesDefinition; + private CheckFactory checkFactory; + + @BeforeEach + public void setUp() { + var activeRules = new ActiveRulesBuilder() + .addRule(new NewActiveRule.Builder() + .setRuleKey(RuleKey.of(DEFAULT_REPOSITORY_KEY, DEFAULT_RULE_KEY)) + .build()) + .addRule(new NewActiveRule.Builder() + .setRuleKey(RuleKey.of(CUSTOM_REPOSITORY_KEY, CUSTOM_RULE_KEY)) + .build()) + .build(); + checkFactory = new CheckFactory(activeRules); + + customRulesDefinition = new MyCustomPlSqlRulesDefinition(); + var context = new RulesDefinition.Context(); + customRulesDefinition.define(context); + } + + @SuppressWarnings("rawtypes") + @Test + void shouldReturnDefaultChecks() { + var checks = CxxChecks.createCxxCheck(checkFactory); + checks.addChecks(DEFAULT_REPOSITORY_KEY, new ArrayList<>(Collections.singletonList(MyRule.class))); + + SquidAstVisitor defaultCheck = check(checks, DEFAULT_REPOSITORY_KEY, DEFAULT_RULE_KEY); + + assertThat(checks.all()).hasSize(1); + assertThat(checks.ruleKey(defaultCheck)).isNotNull(); + assertThat(checks.ruleKey(defaultCheck).rule()).isEqualTo(DEFAULT_RULE_KEY); + assertThat(checks.ruleKey(defaultCheck).repository()).isEqualTo(DEFAULT_REPOSITORY_KEY); + } + + @Test + void shouldReturnCustomChecks() { + var checks = CxxChecks.createCxxCheck(checkFactory); + checks.addCustomChecks(new CustomCxxRulesDefinition[]{customRulesDefinition}); + + SquidAstVisitor customCheck = check(checks, CUSTOM_REPOSITORY_KEY, CUSTOM_RULE_KEY); + + assertThat(checks.all()).hasSize(1); + assertThat(checks.ruleKey(customCheck)).isNotNull(); + assertThat(checks.ruleKey(customCheck).rule()).isEqualTo(CUSTOM_RULE_KEY); + assertThat(checks.ruleKey(customCheck).repository()).isEqualTo(CUSTOM_REPOSITORY_KEY); + } + + @Test + void shouldWorkWithoutCustomChecks() { + var checks = CxxChecks.createCxxCheck(checkFactory); + checks.addCustomChecks(null); + assertThat(checks.all()).isEmpty(); + } + + @SuppressWarnings("rawtypes") + @Test + void shouldNotReturnRuleKeyIfCheckDoesNotExists() { + var checks = CxxChecks.createCxxCheck(checkFactory); + checks.addChecks(DEFAULT_REPOSITORY_KEY, new ArrayList<>(Collections.singletonList(MyRule.class))); + assertThat(checks.ruleKey(new MyCustomRule())).isNull(); + } + + @CheckForNull + public SquidAstVisitor check(CxxChecks cxxChecks, String repository, String rule) { + RuleKey key = RuleKey.of(repository, rule); + + SquidAstVisitor check; + for (var checks : cxxChecks.getChecks()) { + check = checks.of(key); + + if (check != null) { + return check; + } + } + return null; + } + + @Rule(key = DEFAULT_RULE_KEY, name = "This is the default rule", description = "desc") + public static class MyRule extends SquidCheck { + } + + @Rule(key = CUSTOM_RULE_KEY, name = "This is the custom rule", description = "desc") + public static class MyCustomRule extends SquidCheck { + } + + public static class MyCustomPlSqlRulesDefinition extends CustomCxxRulesDefinition { + + private static final Language LANGUAGE = new CxxLanguage(new MapSettings().asConfig()); + + @Override + public String repositoryName() { + return "Custom Rule Repository"; + } + + @Override + public String repositoryKey() { + return CUSTOM_REPOSITORY_KEY; + } + + @SuppressWarnings("rawtypes") + @Override + public Class[] checkClasses() { + return new Class[]{MyCustomRule.class}; + } + + @Override + public Language getLanguage() { + return LANGUAGE; + } + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxFileLinesContextTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxFileLinesContextTest.java new file mode 100644 index 0000000..865b2b5 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxFileLinesContextTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.internal.DefaultNoSonarFilter; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; + +import java.io.File; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CxxFileLinesContextTest { + + private FileLinesContextForTesting fileLinesContext; + + @BeforeEach + public void setUp() throws IOException { + ActiveRules rules = mock(ActiveRules.class); + var checkFactory = new CheckFactory(rules); + FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); + fileLinesContext = new FileLinesContextForTesting(); + when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); + + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx"); + var context = SensorContextTester.create(baseDir); + var inputFile = TestUtils.buildInputFile(baseDir, "ncloc.cc"); + context.fileSystem().add(inputFile); + + var sensor = new CxxSquidSensor(fileLinesContextFactory, checkFactory, new DefaultNoSonarFilter(), null); + sensor.execute(context); + } + + @Test + void TestLinesOfCode() throws UnsupportedEncodingException, IOException { + Set linesOfCode = Stream.of( + 8, 10, 14, 16, 17, 21, 22, 23, 26, 31, 34, 35, 42, 44, 45, 49, 51, 53, 55, 56, + 58, 59, 63, 65, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 79, 82, 84, 86, 87, 89, + 90, 95, 98, 99, 100, 102, 107, 108, 109, 110, 111, 113, 115, 118, 119, 124, 126) + .collect(Collectors.toCollection(HashSet::new)); + + assertThat(fileLinesContext.linesOfCode).containsExactlyInAnyOrderElementsOf(linesOfCode); + } + + @Test + void TestExecutableLinesOfCode() throws UnsupportedEncodingException, IOException { + assertThat(fileLinesContext.executableLines).containsExactlyInAnyOrder( + 10, 26, 34, 35, 56, 59, 69, 70, 72, 73, + 75, 76, 79, 87, 90, 98, 102, 118, 119, 126); + } + + private class FileLinesContextForTesting implements FileLinesContext { + + public final Set executableLines = new HashSet<>(); + public final Set linesOfCode = new HashSet<>(); + + @Override + public void setIntValue(String metricKey, int line, int value) { + assertThat(value).isEqualTo(1); + + switch (metricKey) { + case CoreMetrics.NCLOC_DATA_KEY: + linesOfCode.add(line); + break; + case CoreMetrics.EXECUTABLE_LINES_DATA_KEY: + executableLines.add(line); + break; + default: + fail("Unsupported metric key " + metricKey); + } + } + + @Override + public void setStringValue(String metricKey, int line, String value) { + fail("unexpected method called: setStringValue()"); + } + + @Override + public void save() { + } + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxHighlighterTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxHighlighterTest.java new file mode 100644 index 0000000..cb633ac --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxHighlighterTest.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.sensor.highlighting.TypeOfText; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.internal.DefaultNoSonarFilter; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CxxHighlighterTest { + + private CxxSquidSensor sensor; + private SensorContextTester context; + private DefaultInputFile inputFile; + + @BeforeEach + public void scanFile() throws IOException { + ActiveRules rules = mock(ActiveRules.class); + var checkFactory = new CheckFactory(rules); + FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); + FileLinesContext fileLinesContext = mock(FileLinesContext.class); + when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); + + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx"); + inputFile = TestUtils.buildInputFile(baseDir, "highlighter.cc"); + context = SensorContextTester.create(baseDir); + context.fileSystem().add(inputFile); + + sensor = new CxxSquidSensor(fileLinesContextFactory, checkFactory, new DefaultNoSonarFilter(), null); + sensor.execute(context); + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void keyword() { + + checkOnRange(55, 0, 4, TypeOfText.KEYWORD); // void + checkOnRange(57, 3, 4, TypeOfText.KEYWORD); // auto + checkOnRange(59, 3, 4, TypeOfText.KEYWORD); // auto + checkOnRange(62, 3, 3, TypeOfText.KEYWORD); // for + checkOnRange(62, 7, 5, TypeOfText.KEYWORD); // const + checkOnRange(62, 13, 4, TypeOfText.KEYWORD); // auto + checkOnRange(64, 6, 2, TypeOfText.KEYWORD); // if + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void stringLiteral() { + + checkOnRange(49, 19, 7, TypeOfText.STRING); // "hello" + checkOnRange(50, 19, 18, TypeOfText.STRING); // "hello\tworld\r\n" + checkOnRange(73, 32, 24, TypeOfText.STRING); // R"([.^$|()\[\]{}*+?\\])" + + checkOnRange(83, 24, 5, TypeOfText.STRING); // "..." + checkOnRange(84, 24, 7, TypeOfText.STRING); // u8"..." + checkOnRange(85, 24, 6, TypeOfText.STRING); // L"..." + checkOnRange(86, 24, 6, TypeOfText.STRING); // u"..." + checkOnRange(87, 24, 6, TypeOfText.STRING); // U"..." + + // "hello" " world" + checkOnRange(89, 24, 7, TypeOfText.STRING); + checkOnRange(89, 32, 8, TypeOfText.STRING); + + // u"" "hello world" + checkOnRange(90, 24, 3, TypeOfText.STRING); + checkOnRange(90, 28, 13, TypeOfText.STRING); + + // /*comment1*/ u"" /*comment2*/ "hello world" /*comment3*/; // issue #996 + checkOnRange(91, 24, 12, TypeOfText.COMMENT); + checkOnRange(91, 37, 3, TypeOfText.STRING); + checkOnRange(91, 41, 12, TypeOfText.COMMENT); + checkOnRange(91, 54, 13, TypeOfText.STRING); + checkOnRange(91, 68, 12, TypeOfText.COMMENT); + checkOnRange(91, 82, 13, TypeOfText.COMMENT); + + // /*comment4*/ "hello" + // /*comment5*/ " world" /*comment6*/; + checkOnRange(93, 24, 12, TypeOfText.COMMENT); + checkOnRange(93, 37, 7, TypeOfText.STRING); + checkOnRange(94, 24, 12, TypeOfText.COMMENT); + checkOnRange(94, 37, 8, TypeOfText.STRING); + checkOnRange(94, 46, 12, TypeOfText.COMMENT); + + // "hello" + // "Mary" + // "Lou"; + checkOnRange(96, 25, 7, TypeOfText.STRING); + checkOnRange(97, 25, 6, TypeOfText.STRING); + checkOnRange(98, 25, 5, TypeOfText.STRING); + + // R"(\r\n Hello World!\r\n )" issue #1768 + check(103, 14, TypeOfText.STRING); + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void character() { + + checkOnRange(46, 10, 3, TypeOfText.STRING); // 'x' + checkOnRange(47, 10, 4, TypeOfText.STRING); // '\t' + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void comment() { + + check(1, 0, TypeOfText.COMMENT); + /*\r\n comment\r\n*/ + check(3, 1, TypeOfText.COMMENT); + + checkOnRange(5, 0, 2, TypeOfText.COMMENT); // + checkOnRange(6, 0, 10, TypeOfText.COMMENT); // comment + checkOnRange(7, 0, 2, TypeOfText.COMMENT); // + + checkOnRange(57, 22, 10, TypeOfText.COMMENT); // comment + checkOnRange(58, 3, 10, TypeOfText.COMMENT); // comment + checkOnRange(61, 3, 13, TypeOfText.COMMENT); + /* comment */ + checkOnRange(64, 20, 13, TypeOfText.COMMENT); + /* comment */ + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void number() { + + checkOnRange(27, 10, 1, TypeOfText.CONSTANT); // 0 + checkOnRange(28, 10, 1, TypeOfText.CONSTANT); // -1 (without minus) + checkOnRange(29, 10, 1, TypeOfText.CONSTANT); // +1 (without plus) + + checkOnRange(31, 14, 2, TypeOfText.CONSTANT); // 0u + checkOnRange(32, 19, 3, TypeOfText.CONSTANT); // 1ul + + checkOnRange(34, 9, 3, TypeOfText.CONSTANT); // 0x0 + checkOnRange(35, 9, 3, TypeOfText.CONSTANT); // 0b0 + checkOnRange(36, 9, 16, TypeOfText.CONSTANT); // 0b0100'1100'0110 + + checkOnRange(38, 12, 3, TypeOfText.CONSTANT); // 0.0 + checkOnRange(39, 12, 3, TypeOfText.CONSTANT); // -1.0 (without minus) + checkOnRange(40, 12, 3, TypeOfText.CONSTANT); // +1.0 (without plus) + checkOnRange(41, 12, 8, TypeOfText.CONSTANT); // 3.14E-10 + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void preprocessDirective() { + + checkOnRange(12, 0, 8, TypeOfText.PREPROCESS_DIRECTIVE); // #include + + checkOnRange(14, 0, 6, TypeOfText.PREPROCESS_DIRECTIVE); // #ifdef + checkOnRange(15, 0, 10, TypeOfText.PREPROCESS_DIRECTIVE); // # define + checkOnRange(16, 0, 5, TypeOfText.PREPROCESS_DIRECTIVE); // #else + checkOnRange(17, 0, 10, TypeOfText.PREPROCESS_DIRECTIVE); // # define + checkOnRange(18, 0, 6, TypeOfText.PREPROCESS_DIRECTIVE); // #endif + + checkOnRange(20, 0, 7, TypeOfText.PREPROCESS_DIRECTIVE); // #define + } + + @Test + @SuppressWarnings("squid:S2699") // ... checkOnRange contains the assertion + void identifiersWithSpecialMeaning() { + // identifier with special meaning => no highlighting + checkOnRange(112, 11, 6, null); // import + checkOnRange(119, 10, 6, null); // module + checkOnRange(120, 12, 6, null); // module + checkOnRange(122, 13, 6, null); // module + } + + /** + * Checks the highlighting of a range of columns. The first column of a line has index 0. The range is the columns of + * the token. + */ + private void checkOnRange(int line, int firstColumn, int length, TypeOfText expectedTypeOfText) { + // check that every column of the token is highlighted (and with the expected type) + for (var column = firstColumn; column < firstColumn + length; column++) { + checkInternal(line, column, "", expectedTypeOfText); + } + + // check that the column before the token is not highlighted + if (firstColumn != 0) { + checkInternal(line, firstColumn - 1, " (= before the token)", null); + } + + // check that the column after the token is not highlighted + checkInternal(line, firstColumn + length, " (= after the token)", null); + } + + /** + * Checks the highlighting of one column. The first column of a line has index 0. + */ + private void check(int line, int column, TypeOfText expectedTypeOfText) { + checkInternal(line, column, "", expectedTypeOfText); + } + + private void checkInternal(int line, int column, String messageComplement, TypeOfText expectedTypeOfText) { + List foundTypeOfTexts = context.highlightingTypeAt( + "ProjectKey:" + inputFile.file().getName(), line, column); + + int expectedNumberOfTypeOfText = expectedTypeOfText == null ? 0 : 1; + var message = "number of TypeOfTexts at line " + line + " and column " + column + messageComplement; + assertThat(foundTypeOfTexts).as(message).hasSize(expectedNumberOfTypeOfText); + if (expectedNumberOfTypeOfText > 0) { + message = "found TypeOfTexts at line " + line + " and column " + column + messageComplement; + assertThat(foundTypeOfTexts.get(0)).as(message).isEqualTo(expectedTypeOfText); + } + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxLanguageTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxLanguageTest.java new file mode 100644 index 0000000..6079b48 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxLanguageTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.sonar.api.config.internal.MapSettings; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxLanguageTest { + + private final MapSettings settings = new MapSettings(); + + @Test + void testCxxLanguageStringConfiguration() throws Exception { + var language = new CxxLanguage(settings.asConfig()); + assertThat(language.getKey()).isEqualTo("cxx"); + } + + @Test + void shouldReturnConfiguredFileSuffixes() { + settings.setProperty(CxxLanguage.FILE_SUFFIXES_KEY, ".C,.c,.H,.h"); + var cxx = new CxxLanguage(settings.asConfig()); + String[] expected = {".C", ".c", ".H", ".h"}; + assertThat(cxx.getFileSuffixes()).contains(expected); + } + + @Test + void shouldReturnDefaultFileSuffixes1() { + var cxx = new CxxLanguage(settings.asConfig()); + String[] expected = {"disabled"}; + assertThat(cxx.getFileSuffixes()).contains(expected); + } + + @Test + void shouldReturnDefaultFileSuffixes2() { + settings.setProperty(CxxLanguage.FILE_SUFFIXES_KEY, ""); + var cxx = new CxxLanguage(settings.asConfig()); + String[] expected = {"disabled"}; + assertThat(cxx.getFileSuffixes()).contains(expected); + } + + @Test + void shouldBeDisabled() { + settings.setProperty(CxxLanguage.FILE_SUFFIXES_KEY, "-"); + var cxx = new CxxLanguage(settings.asConfig()); + String[] expected = {"disabled"}; + assertThat(cxx.getFileSuffixes()).contains(expected); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxMetricDefinitionTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxMetricDefinitionTest.java new file mode 100644 index 0000000..9861c42 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxMetricDefinitionTest.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxMetricDefinitionTest { + + @Test + void metrics_defined() { + assertThat(new CxxMetricDefinition().getMetrics()).hasSize(12); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxPluginTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxPluginTest.java new file mode 100644 index 0000000..1081029 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxPluginTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.sonar.api.Plugin; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.Version; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxPluginTest { + + @Test + void testGetExtensions() throws Exception { + SonarRuntime runtime = SonarRuntimeImpl.forSonarQube( + Version.create(8, 6), + SonarQubeSide.SCANNER, + SonarEdition.COMMUNITY + ); + var context = new Plugin.Context(runtime); + var plugin = new CxxPlugin(); + plugin.define(context); + assertThat(context.getExtensions()).hasSize(84); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxRuleRepositoryTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxRuleRepositoryTest.java new file mode 100644 index 0000000..c11baf3 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxRuleRepositoryTest.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.rule.RulesDefinition; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxRuleRepositoryTest { + + @Test + void rulesTest() { + var context = new RulesDefinition.Context(); + assertThat(context.repositories()).isEmpty(); + new CxxRuleRepository().define(context); + + assertThat(context.repositories()).hasSize(1); + assertThat(context.repository("cxx").rules()).hasSize(27); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSonarWayProfileTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSonarWayProfileTest.java new file mode 100644 index 0000000..7b09ca4 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSonarWayProfileTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.sonar.api.server.profile.BuiltInQualityProfilesDefinition; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class CxxSonarWayProfileTest { + + @Test + void should_create_sonar_way_profile() { + var profileDef = new CxxSonarWayProfile(); + var context = new BuiltInQualityProfilesDefinition.Context(); + profileDef.define(context); + BuiltInQualityProfilesDefinition.BuiltInQualityProfile profile = context.profile("cxx", "Sonar way"); + assertThat(profile.language()).isEqualTo(CxxLanguage.KEY); + assertThat(profile.name()).isEqualTo("Sonar way"); + List activeRules = profile.rules(); + assertThat(activeRules.size()).as("Expected number of rules in profile").isNotNegative(); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSquidSensorTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSquidSensorTest.java new file mode 100644 index 0000000..4d6aa76 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/CxxSquidSensorTest.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.rule.ActiveRules; +import org.sonar.api.batch.rule.CheckFactory; +import org.sonar.api.batch.sensor.cpd.internal.TokensLine; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.batch.sensor.issue.internal.DefaultNoSonarFilter; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.FileLinesContext; +import org.sonar.api.measures.FileLinesContextFactory; +import org.sonar.cxx.CxxMetrics; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class CxxSquidSensorTest { + + private CxxSquidSensor sensor; + private final MapSettings settings = new MapSettings(); + + @BeforeEach + public void setUp() { + ActiveRules rules = mock(ActiveRules.class); + var checkFactory = new CheckFactory(rules); + FileLinesContextFactory fileLinesContextFactory = mock(FileLinesContextFactory.class); + FileLinesContext fileLinesContext = mock(FileLinesContext.class); + when(fileLinesContextFactory.createFor(Mockito.any(InputFile.class))).thenReturn(fileLinesContext); + + sensor = new CxxSquidSensor(fileLinesContextFactory, checkFactory, new DefaultNoSonarFilter(), null); + } + + @Test + void testCollectingSquidMetrics() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/codechunks-project"); + var inputFile0 = TestUtils.buildInputFile(baseDir, "code_chunks.cc"); + + var context = SensorContextTester.create(baseDir); + context.fileSystem().add(inputFile0); + sensor.execute(context); + + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.NCLOC).value()).isEqualTo(54); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.STATEMENTS).value()).isEqualTo(50); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.FUNCTIONS).value()).isEqualTo(7); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.CLASSES).value()).isZero(); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.COMPLEXITY).value()).isEqualTo(19); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.COGNITIVE_COMPLEXITY).value()).isEqualTo(8); + softly.assertThat(context.measure(inputFile0.key(), CoreMetrics.COMMENT_LINES).value()).isEqualTo(15); + softly.assertAll(); + } + + @Test + void testCpdTokens() throws Exception { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx"); + var context = SensorContextTester.create(baseDir); + settings.setProperty(CxxSquidSensor.CPD_IGNORE_IDENTIFIERS_KEY, true); + settings.setProperty(CxxSquidSensor.CPD_IGNORE_LITERALS_KEY, true); + context.setSettings(settings); + + var inputFile = TestUtils.buildInputFile(baseDir, "cpd.cc"); + context.fileSystem().add(inputFile); + sensor.execute(context); + + List cpdTokenLines = context.cpdTokens("ProjectKey:" + inputFile.file().getName()); + assertThat(cpdTokenLines).hasSize(75); + + // ld &= 0xFF; + var firstTokensLine = cpdTokenLines.get(2); + assertThat(firstTokensLine.getValue()).isEqualTo("_I&=_N;"); + assertThat(firstTokensLine.getStartLine()).isEqualTo(4); + assertThat(firstTokensLine.getStartUnit()).isEqualTo(10); + assertThat(firstTokensLine.getEndLine()).isEqualTo(4); + assertThat(firstTokensLine.getEndUnit()).isEqualTo(13); + + // if (xosfile_read_stamped_no_path(fn, &ob, 1, 1, 1, 1, 1)) return 1; + var secondTokensLine = cpdTokenLines.get(48); + assertThat(secondTokensLine.getValue()).isEqualTo("if(_I(_I,&_I,_N,_N,_N,_N,_N))return_N;"); + assertThat(secondTokensLine.getStartLine()).isEqualTo(60); + assertThat(secondTokensLine.getStartUnit()).isEqualTo(283); + assertThat(secondTokensLine.getEndLine()).isEqualTo(60); + assertThat(secondTokensLine.getEndUnit()).isEqualTo(305); + + // case 3: return "three"; + var thirdTokensLine = cpdTokenLines.get(71); + assertThat(thirdTokensLine.getValue()).isEqualTo("case_N:return_S;"); + assertThat(thirdTokensLine.getStartLine()).isEqualTo(86); + assertThat(thirdTokensLine.getStartUnit()).isEqualTo(381); + assertThat(thirdTokensLine.getEndLine()).isEqualTo(86); + assertThat(thirdTokensLine.getEndUnit()).isEqualTo(386); + } + + @Test + void testComplexitySquidMetrics() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/complexity-project"); + var context = SensorContextTester.create(baseDir); + settings.setProperty(CxxSquidSensor.FUNCTION_COMPLEXITY_THRESHOLD_KEY, 3); + settings.setProperty(CxxSquidSensor.FUNCTION_SIZE_THRESHOLD_KEY, 3); + context.setSettings(settings); + + var inputFile = TestUtils.buildInputFile(baseDir, "complexity.cc"); + context.fileSystem().add(inputFile); + sensor.execute(context); + + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.FUNCTIONS).value()).isEqualTo(22); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.CLASSES).value()).isEqualTo(2); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.COMPLEXITY).value()).isEqualTo(38); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.COGNITIVE_COMPLEXITY).value()).isEqualTo(16); + + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.COMPLEX_FUNCTIONS).value()).isEqualTo(1); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.COMPLEX_FUNCTIONS_PERC)).isNull(); // see DensityMeasureComputer + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.COMPLEX_FUNCTIONS_LOC).value()).isEqualTo(6); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.COMPLEX_FUNCTIONS_LOC_PERC)).isNull(); // see DensityMeasureComputer + + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.LOC_IN_FUNCTIONS).value()).isEqualTo(59); + + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.BIG_FUNCTIONS).value()).isEqualTo(9); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.BIG_FUNCTIONS_PERC)).isNull(); // see DensityMeasureComputer + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.BIG_FUNCTIONS_LOC).value()).isEqualTo(44); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.BIG_FUNCTIONS_LOC_PERC)).isNull(); // see DensityMeasureComputer + softly.assertAll(); + } + + @Test + void testDocumentationSquidMetrics() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/documentation-project"); + var inputFile = TestUtils.buildInputFile(baseDir, "documentation0.hh"); + + var context = SensorContextTester.create(baseDir); + context.fileSystem().add(inputFile); + sensor.execute(context); + + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.PUBLIC_API_KEY).value()).isEqualTo(8); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.PUBLIC_UNDOCUMENTED_API_KEY).value()).isEqualTo(2); + softly.assertThat(context.measure(inputFile.key(), CxxMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY)).isNull(); // see DensityMeasureComputer + + String moduleKey = context.project().key(); + softly.assertThat(context.measure(moduleKey, CxxMetrics.PUBLIC_API_KEY)).isNull(); // see AggregateMeasureComputer + softly.assertThat(context.measure(moduleKey, CxxMetrics.PUBLIC_UNDOCUMENTED_API_KEY)).isNull(); // see AggregateMeasureComputer + softly.assertThat(context.measure(moduleKey, CxxMetrics.PUBLIC_DOCUMENTED_API_DENSITY_KEY)).isNull(); // see AggregateMeasureComputer + softly.assertAll(); + } + + @Test + void testReplacingOfExtenalMacros() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/external-macro-project"); + var context = SensorContextTester.create(baseDir); + settings.setProperty(CxxSquidSensor.DEFINES_KEY, "MACRO class A{};"); + context.setSettings(settings); + + var inputFile = TestUtils.buildInputFile(baseDir, "test.cc"); + context.fileSystem().add(inputFile); + sensor.execute(context); + + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(1); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.STATEMENTS).value()).isZero(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.FUNCTIONS).value()).isZero(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.CLASSES).value()).isEqualTo(1); + softly.assertAll(); + } + + @Test + void testFindingIncludedFiles() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/include-directories-project"); + var context = SensorContextTester.create(baseDir); + settings.setProperty(CxxSquidSensor.INCLUDE_DIRECTORIES_KEY, "include"); + context.setSettings(settings); + + var inputFile = TestUtils.buildInputFile(baseDir, "src/main.cc"); + context.fileSystem().add(inputFile); + sensor.execute(context); + + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(9); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.STATEMENTS).value()).isZero(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.FUNCTIONS).value()).isEqualTo(9); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.CLASSES).value()).isZero(); + softly.assertAll(); + + } + + @Test + void testForceIncludedFiles() throws IOException { + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/force-include-project"); + var context = SensorContextTester.create(baseDir); + settings.setProperty(CxxSquidSensor.INCLUDE_DIRECTORIES_KEY, "include"); + settings.setProperty(CxxSquidSensor.FORCE_INCLUDES_KEY, "force1.hh,subfolder/force2.hh"); + context.setSettings(settings); + + var inputFile = TestUtils.buildInputFile(baseDir, "src/src1.cc"); + context.fileSystem().add(inputFile); + sensor.execute(context); + + // These checks actually check the force include feature, since only if it works the metric values will be like follows + var softly = new SoftAssertions(); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(1); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.STATEMENTS).value()).isEqualTo(2); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.FUNCTIONS).value()).isEqualTo(1); + softly.assertThat(context.measure(inputFile.key(), CoreMetrics.CLASSES).value()).isZero(); + softly.assertAll(); + } + + @Test + void testBehaviourOnCircularIncludes() throws IOException { + // especially: when two files, both belonging to the set of + // files to analyse, include each other, the preprocessor guards have to be disabled + // and both have to be counted in terms of metrics + File baseDir = TestUtils.loadResource("/com/keyware/sonar/cxx/circular-includes-project"); + var inputFile = TestUtils.buildInputFile(baseDir, "test1.hh"); + + var context = SensorContextTester.create(baseDir); + context.fileSystem().add(inputFile); + sensor.execute(context); + + assertThat(context.measure(inputFile.key(), CoreMetrics.NCLOC).value()).isEqualTo(1); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/DroppedPropertiesSensorTest.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/DroppedPropertiesSensorTest.java new file mode 100644 index 0000000..0ac3af7 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/DroppedPropertiesSensorTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.TempDir; +import org.sonar.api.batch.sensor.internal.SensorContextTester; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.log.LogTesterJUnit5; +import org.sonar.api.utils.log.LoggerLevel; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class DroppedPropertiesSensorTest { + + @TempDir + File tempDir; + + @RegisterExtension + public LogTesterJUnit5 logTester = new LogTesterJUnit5(); + + @Test + void testNoMsg() throws Exception { + var contextTester = SensorContextTester.create(tempDir); + var mapSettings = new MapSettings().setProperty("sonar.cxx.xxx", "value"); + contextTester.setSettings(mapSettings); + List analysisWarnings = new ArrayList<>(); + var sensor = new DroppedPropertiesSensor(analysisWarnings::add); + sensor.execute(contextTester); + + assertThat(logTester.logs(LoggerLevel.WARN)).isEmpty(); + assertThat(analysisWarnings).isEmpty(); + } + + @Test + void testNoLongerSupported() throws Exception { + var contextTester = SensorContextTester.create(tempDir); + var mapSettings = new MapSettings().setProperty("sonar.cxx.cppncss.reportPaths", "value"); + contextTester.setSettings(mapSettings); + List analysisWarnings = new ArrayList<>(); + var sensor = new DroppedPropertiesSensor(analysisWarnings::add); + sensor.execute(contextTester); + + var msg = "CXX property 'sonar.cxx.cppncss.reportPaths' is no longer supported."; + assertThat(logTester.logs(LoggerLevel.WARN)).contains(msg); + assertThat(analysisWarnings).containsExactly(msg); + } + + @Test + void testNoLongerSupportedWithInfo() throws Exception { + var contextTester = SensorContextTester.create(tempDir); + var mapSettings = new MapSettings().setProperty("sonar.cxx.suffixes.sources", "value"); + contextTester.setSettings(mapSettings); + List analysisWarnings = new ArrayList<>(); + var sensor = new DroppedPropertiesSensor(analysisWarnings::add); + sensor.execute(contextTester); + + var msg = "CXX property 'sonar.cxx.suffixes.sources' is no longer supported." + + " Use key 'sonar.cxx.file.suffixes' instead."; + assertThat(logTester.logs(LoggerLevel.WARN)).contains(msg); + assertThat(analysisWarnings).containsExactly(msg); + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/TestUtils.java b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/TestUtils.java new file mode 100644 index 0000000..d3b18e7 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/java/com/keyware/sonar/cxx/TestUtils.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2023 - 2024. KeyWare.Co.Ltd All rights reserved. + * 项目名称:C++ 信息安全性设计准则 + * 项目描述:用于检查C++源代码的安全性设计准则的Sonarqube插件 + * 版权说明:本软件属北京关键科技股份有限公司所有,在未获得北京关键科技股份有限公司正式授权情况下,任何企业和个人,不能获取、阅读、安装、传播本软件涉及的任何受知识产权保护的内容。 + */ +package com.keyware.sonar.cxx; + +import org.assertj.core.util.Files; +import org.sonar.api.batch.fs.InputFile; +import org.sonar.api.batch.fs.internal.DefaultInputFile; +import org.sonar.api.batch.fs.internal.TestInputFileBuilder; + +import javax.annotation.CheckForNull; +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +public final class TestUtils { + + public static File loadResource(String resourceName) { + URL resource = TestUtils.class.getResource(resourceName); + File resourceAsFile = null; + try { + resourceAsFile = new File(resource.toURI()); + } catch (URISyntaxException e) { + System.out.println("Cannot load resource: " + resourceName); + } + + return resourceAsFile; + } + + /** + * Search for a test resource in the classpath. For example getResource("org/sonar/MyClass/foo.txt"); + * + * @param path the starting slash is optional + * @return the resource. Null if resource not found + */ + @CheckForNull + public static File getResource(String path) { + String resourcePath = path; + if (!resourcePath.startsWith("/")) { + resourcePath = "/" + resourcePath; + } + URL url = TestUtils.class.getResource(resourcePath); + if (url != null) { + try { + return new File(url.toURI()); + } catch (URISyntaxException e) { + return null; + } + } + return null; + } + + public static DefaultInputFile buildInputFile(File baseDir, String fileName) throws IOException { + var target = new File(baseDir, fileName); + String content = Files.contentOf(target, StandardCharsets.UTF_8); + var inputFile = TestInputFileBuilder.create("ProjectKey", baseDir, target) + .setContents(content) + .setCharset(StandardCharsets.UTF_8) + .setLanguage("cxx") + .setType(InputFile.Type.MAIN).build(); + return inputFile; + } + + private TestUtils() { + // utility class + } + +} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/sonar-project.properties new file mode 100644 index 0000000..1b6e9f1 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/sonar-project.properties @@ -0,0 +1,12 @@ +# required metadata +sonar.projectKey=TEST_circular_includes +sonar.projectName=TEST_circular_includes +sonar.projectVersion=0.0.1 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sonar.sources=. + diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test1.hh new file mode 100644 index 0000000..bae62af --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test1.hh @@ -0,0 +1,5 @@ +#ifndef TEST1 +#define TEST1 +#include "test2.hh" +void test1(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test2.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test2.hh new file mode 100644 index 0000000..7e7ab4e --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-includes-project/test2.hh @@ -0,0 +1,5 @@ +#ifndef TEST2 +#define TEST2 +#include "test1.hh" +void test2(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package1/test1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package1/test1.hh new file mode 100644 index 0000000..4f3956d --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package1/test1.hh @@ -0,0 +1,5 @@ +#ifndef TEST1 +#define TEST1 +#include "../Package2/test2.hh" +void test1(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test2.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test2.hh new file mode 100644 index 0000000..5e4e9f4 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test2.hh @@ -0,0 +1,4 @@ +#ifndef TEST2 +#define TEST2 +void test2(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test3.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test3.hh new file mode 100644 index 0000000..1f58a17 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test3.hh @@ -0,0 +1,6 @@ +#ifndef TEST3 +#define TEST3 +#include "../Package1/test1.hh" +#include "test2.hh" +void test3(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test4.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test4.hh new file mode 100644 index 0000000..272aab9 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/Package2/test4.hh @@ -0,0 +1,5 @@ +#ifndef TEST4 +#define TEST4 +#include "../Package1/test1.hh" +void test4(); +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/sonar-project.properties new file mode 100644 index 0000000..07aee07 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/circular-packages-project/sonar-project.properties @@ -0,0 +1,12 @@ +# required metadata +sonar.projectKey=TEST_circular_packages +sonar.projectName=TEST_circular_packages +sonar.projectVersion=0.0.1 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sonar.sources=. + diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/code_chunks.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/code_chunks.cc new file mode 100644 index 0000000..69babad --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/code_chunks.cc @@ -0,0 +1,91 @@ +/* Fudge unix isatty and fileno for RISCOS */ + +#include "unixstuff.h" +#include +#include +#include "oslib/osfile.h" + +int fileno(FILE *f) +{ return (int)f; +} + +int isatty(int fn) +{ return (fn==fileno(stdin)); +} + +bits unixtime(bits ld,bits ex) +{ ld&=0xFF; + ld-=51; + if(ex<1855547904U) ld--; + ex-=1855548004U; + return ex/100+42949673U*ld-ld/25; +} + + +/* from RISC OS infozip, preserves filetype in ld */ +int acorntime(bits *ex, bits *ld, time_t utime) +{ + unsigned timlo; /* 3 lower bytes of acorn file-time plus carry byte */ + unsigned timhi; /* 2 high bytes of acorn file-time */ + + timlo = ((unsigned)utime & 0x00ffffffU) * 100 + 0x00996a00U; + timhi = ((unsigned)utime >> 24); + timhi = timhi * 100 + 0x0000336eU + (timlo >> 24); + if (timhi & 0xffff0000U) + return 1; /* calculation overflow, do not change time */ + + /* insert the five time bytes into loadaddr and execaddr variables */ + *ex = (timlo & 0x00ffffffU) | ((timhi & 0x000000ffU) << 24); + *ld = (*ld & 0xffffff00U) | ((timhi >> 8) & 0x000000ffU); + + return 0; /* subject to future extension to signal overflow */ +} + + +int isdir(char *fn) +{ int ob; + if(xosfile_read_stamped_no_path(fn,&ob,0,0,0,0,0)) return 0; + switch (ob) + { case osfile_IS_DIR:return 1; + case osfile_IS_IMAGE:return 1; + } + return 0; +} + +int isfile(char *fn) +{ int ob; + if(xosfile_read_stamped_no_path(fn,&ob,0,0,0,0,0)) return 0; + switch (ob) + { case osfile_IS_FILE:return 1; + case osfile_IS_IMAGE:return 1; + } + return 0; +} + +int object_exists(char *fn) +{ int ob; + if(xosfile_read_stamped_no_path(fn,&ob,0,0,0,0,0)) return 0; + switch (ob) + { case osfile_IS_FILE:return 1; + case osfile_IS_DIR:return 1; + case osfile_IS_IMAGE:return 1; + } + return 0; +} + +// int object_exists(char *fn) +// { int ob; +// if(xosfile_read_stamped_no_path(fn,&ob,0,0,0,0,0)) return 0; +// switch (ob) +// { case osfile_IS_FILE:return 1; +// case osfile_IS_DIR:return 1; +// case osfile_IS_IMAGE:return 1; +// } +// return 0; +// } + +// +// +// +// +// diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/sonar-project.properties new file mode 100644 index 0000000..e1def5c --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/codechunks-project/sonar-project.properties @@ -0,0 +1,10 @@ +# required metadata +sonar.projectKey=SampleProject +sonar.projectName=SampleProject +sonar.projectVersion=1.0 + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sources=. diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/complexity.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/complexity.cc new file mode 100644 index 0000000..1700648 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/complexity.cc @@ -0,0 +1,128 @@ +void test_function_definition() { // 1 +} + +int test_return() { // 1 + return 1; +} + +int test_if(int p) { // 2 + if( p ) { + return 1; + } + return 0; +} + +int test_if_else(int p) { // 2 + if( p ) { + return 1; + } + else { + return 0; + } +} + +int test_or(int p1, int p2) { // 3 + if( p1 || p2 ) { + return 1; + } + else { + return 0; + } +} + +int test_and(int p1, int p2) { // 3 + if( p1 && p2 ) { + return 1; + } + else { + return 0; + } +} + +int test_quest(int p) { // 2 + return p ? 1 : 0; +} + +void test_while() { // 2 + int i = 10; + while(i) { + --i; + } +} + +void test_for() { // 2 + int j = 0; + for(int i=0; i<10; ++i) { + ++j; + } +} + +int test_switch_case(int p) { // 5 + switch(p) { + case 1: return 1; + case 2: return 2; + case 3: return 3; + default: return -1; + } +} + +int test_catch1() { // 2 + int i = 0; + try { + i = i / 0; + } + catch(...) { + i = 0; + } +} + +int test_catch2() { // 3 + int i = 0; + try { + i = i / 0; + } + catch(const exception& e) { + i = 0; + } + catch(...) { + i = 0; + } +} + +class ClassA { // 5 + ClassA() {} + ~ClassA() {} + void test_method1() {} + void test_method2() {} + void test_method3() {} +}; + +class ClassB { // definition outside + ClassB(); + ~ClassB(); + void test_method1(); + void test_method2(); + void test_method3(); +}; + +ClassB::ClassB() { // 1 +} + +ClassB::~ClassB() { // 1 +} + +void ClassB::test_method1() { // 1 +} + +void ClassB::test_method2() { // 1 +} + +void ClassB::test_method3() { // 1 +} + +// summary: +// functions/methods : 22 +// classes : 2 +// complexity : 38 +// complexity in functions/methods: 38 +// complexity in classes : 10 diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/sonar-project.properties new file mode 100644 index 0000000..0dea261 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/complexity-project/sonar-project.properties @@ -0,0 +1,11 @@ +# required metadata +sonar.projectKey=SampleProject +sonar.projectName=SampleProject +sonar.projectVersion=1.0 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sources=. diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/cpd.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/cpd.cc new file mode 100644 index 0000000..d2fb78f --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/cpd.cc @@ -0,0 +1,104 @@ +//------------------------------------ +bits unixtime1(bits ld, bits ex) +{ + ld &= 0xFF; + ld -= 51; + if (ex < 1855547904U) ld--; + ex -= 1855548004U; + return ex / 100 + 42949673U * ld - ld / 25; +} + +bits unixtime2(bits ld, bits ex) +{ + if (ld && ex) + { + ld &= 0xFF; + ld -= 51; + if (ex < 1855547904U) ld--; + ex -= 1855548004U; + return ex / 100 + 42949673U * ld - ld / 25; + } + return 0; +} + +//------------------------------------ +int acorntime(bits *ex, bits *ld, time_t utime) +{ + unsigned timlo; /* 3 lower bytes of acorn file-time plus carry byte */ + unsigned timhi; /* 2 high bytes of acorn file-time */ + + timlo = ((unsigned)utime & 0x00ffffffU) * 100 + 0x00996a00U; + timhi = ((unsigned)utime >> 24); + timhi = timhi * 100 + 0x0000336eU + (timlo >> 24); + if (timhi & 0xffff0000U) + return 1; /* calculation overflow, do not change time */ + + /* insert the five time bytes into loadaddr and execaddr variables */ + *ex = (timlo & 0x00ffffffU) | ((timhi & 0x000000ffU) << 24); + *ld = (*ld & 0xffffff00U) | ((timhi >> 8) & 0x000000ffU); + + return 0; /* subject to future extension to signal overflow */ +} + +//------------------------------------ +int object_exists1(char *fn) +{ + int ob; + if (xosfile_read_stamped_no_path(fn, &ob, 0, 0, 0, 0, 0)) return 0; + switch (ob) + { + case osfile_IS_FILE:return 1; + case osfile_IS_DIR:return 1; + case osfile_IS_IMAGE:return 1; + } + return 0; +} + +int object_exists2(char *fn) +{ + int ob; + if (xosfile_read_stamped_no_path(fn, &ob, 1, 1, 1, 1, 1)) return 1; + switch (ob) + { + case osfile_IS_FILE:return 2; + case osfile_IS_DIR:return 2; + case osfile_IS_IMAGE:return 2; + } + return 0; +} + +//------------------------------------ +char* tostring1(int value) +{ + switch (value) + { + case 0: return "zero"; + case 1: return "one"; + } + return "undefined"; +} + +char* tostring2(int value) +{ + switch (value) + { + case 2: return "two"; + case 3: return "three"; + } + return "undefined"; +} + +// CPD should ignore declarations +class A { +public: + virtual void a(); + virtual void b(); + virtual void c(); +}; + +class B : public A { +public: + virtual void a(); + virtual void b(); + virtual void c(); +}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/documentation0.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/documentation0.hh new file mode 100644 index 0000000..1ab9089 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/documentation0.hh @@ -0,0 +1,36 @@ +#ifndef DOCUMENTATION0_H +#define DOCUMENTATION0_H + +/** + * Encoding mode + */ +enum Mode { //1 + /** + * stereo encoding + */ + STEREO = 0, //2 + /** + * joint frequency encoding + */ + JOINT_STEREO, //3 + /** + * mono encoding + */ + MONO //4 +}; + +using RC = int; //5 [undocumented] + +RC init(); //6 [undocumented] + +/** + * Decode buffer of given length + */ +RC decode( unsigned char* buf, int len ); //7 + +/** + * Encode buffer of given length using the mode + */ +RC encode( unsigned char* buf, int len, Mode mode ); //8 + +#endif diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/sonar-project.properties new file mode 100644 index 0000000..0dea261 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/documentation-project/sonar-project.properties @@ -0,0 +1,11 @@ +# required metadata +sonar.projectKey=SampleProject +sonar.projectName=SampleProject +sonar.projectVersion=1.0 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sources=. diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/external-macro-project/test.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/external-macro-project/test.cc new file mode 100644 index 0000000..6132ce7 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/external-macro-project/test.cc @@ -0,0 +1 @@ +MACRO diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/sonar-project.properties new file mode 100644 index 0000000..0662bbe --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/sonar-project.properties @@ -0,0 +1,15 @@ +# required metadata +sonar.projectKey=project_finding_sources +sonar.projectName=project_finding_sources +sonar.projectVersion=0.0.1 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sonar.sources=src +sonar.tests=tests1,tests2 + +# paths to the reports +sonar.cxx.xunit.reportPaths=xunit-report.xml diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/src/.dummy b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/src/.dummy new file mode 100644 index 0000000..e69de29 diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test1.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test1.cc new file mode 100644 index 0000000..c3c3363 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test1.cc @@ -0,0 +1 @@ +class TestClass1{}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test5.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test5.cc new file mode 100644 index 0000000..775c00d --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test5.cc @@ -0,0 +1,2 @@ +class TestClass5_A{}; +class TestClass5_B{}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6.hh new file mode 100644 index 0000000..a66fd18 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6.hh @@ -0,0 +1,4 @@ +class TestClass6{ + void test_foo(); + void test_bar(); +}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_A.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_A.cc new file mode 100644 index 0000000..fa08454 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_A.cc @@ -0,0 +1,2 @@ +#include "Test6.hh" +void TestClass6::test_foo(){}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_B.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_B.cc new file mode 100644 index 0000000..183e389 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test6_B.cc @@ -0,0 +1,2 @@ +#include "Test6.hh" +void TestClass6::test_bar(){}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test7.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test7.cc new file mode 100644 index 0000000..4c89d8c --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/Test7.cc @@ -0,0 +1,4 @@ +namespace my_test_suite { + struct my_test { void test_method(); }; + void my_test::test_method() {} +} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/subdir/Test2.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/subdir/Test2.cc new file mode 100644 index 0000000..4324e3c --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests1/subdir/Test2.cc @@ -0,0 +1 @@ +class TestClass2{}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test3.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test3.cc new file mode 100644 index 0000000..34b2045 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test3.cc @@ -0,0 +1,3 @@ +namespace ns{ + class TestClass3{}; +}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.cc new file mode 100644 index 0000000..4c10b79 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.cc @@ -0,0 +1,2 @@ +#include "Test4.hh" +void ns::TestClass4::test1(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.hh new file mode 100644 index 0000000..cc9e5e9 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/tests2/Test4.hh @@ -0,0 +1,5 @@ +namespace ns{ + class TestClass4{ + void test1(); + }; +}; diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/xunit-report.xml b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/xunit-report.xml new file mode 100644 index 0000000..087f325 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/finding-sources-project/xunit-report.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/force1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/force1.hh new file mode 100644 index 0000000..6cebdad --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/force1.hh @@ -0,0 +1 @@ +#define MACRO1(a) if(a); diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/subfolder/force2.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/subfolder/force2.hh new file mode 100644 index 0000000..4d14df1 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/include/subfolder/force2.hh @@ -0,0 +1 @@ +#define MACRO2(a) void func() { MACRO1(a) } diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/sonar-project.properties new file mode 100644 index 0000000..0aa3943 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/sonar-project.properties @@ -0,0 +1,14 @@ +# required metadata +sonar.projectKey=TEST_force_includes +sonar.projectName=TEST_force_includes +sonar.projectVersion=0.0.1 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sonar.sources=src + +sonar.cxx.includeDirectories=include +sonar.cxx.forceIncludes=force1.hh,subfolder/force2.hh diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/scr2.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/scr2.cc new file mode 100644 index 0000000..94151c7 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/scr2.cc @@ -0,0 +1 @@ +MACRO2(2) \ No newline at end of file diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/src1.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/src1.cc new file mode 100644 index 0000000..9f322ff --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/force-include-project/src/src1.cc @@ -0,0 +1 @@ +MACRO2(1) \ No newline at end of file diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.cc new file mode 100644 index 0000000..6192852 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.cc @@ -0,0 +1,125 @@ +/* + comment +*/ + +// +// comment +// + +// +// PREPROCESS_DIRECTIVE +// +#include "highlighter.h" + +#ifdef _TEST +# define VALUE 1 +#else +# define VALUE 2 +#endif + +#define FUNC(a) \ + auto value = a; \ + return value ? 0 : 1; + +// +// CONSTANT +// +int i1 = 0; +int i2 = -1; +int i3 = +1; + +unsigned u1 = 0u; +unsigned long u2 = 1ul; + +int h1 = 0x0; +int b1 = 0b0; +int b2 = 0b0100'1100'0110; + +float f1 = 0.0; +float f2 = -1.0; +float f3 = +1.0; +float f4 = 3.14E-10; + +// +// STRING +// +char c1 = 'x'; +char c1 = '\t'; + +const char* str1 = "hello"; +const char* str1 = "hello\tworld\r\n"; + +// +// KEYWORD +// +void func() +{ + auto str = "test"; // comment + // comment + auto iii = 0; + + /* comment */ + for(const auto& c : str) + { + if (c == 't') /* comment */ + { + iii++; + } + } +} + +void test1() +{ + const std::regex RegexEscape(R"([.^$|()\[\]{}*+?\\])"); // raw string literal +} + +void test2(const char* sourceFilename) +{ + Warning() << "Failed to open file " << sourceFilename << " for reading"; +} + +void test3() +{ + const char *t1 = "..."; // UTF-8 encoded + const char *t2 = u8"..."; // UTF-8 encoded + const wchar_t *t3 = L"..."; // Wide string + const char16_t *t4 = u"..."; // UTF-16 encoded + const char32_t *t5 = U"..."; // UTF-32 encoded + + const char *t6 = "hello" " world"; + const wchar_t *t7 = u"" "hello world"; + const wchar_t *t8 = /*comment1*/ u"" /*comment2*/ "hello world" /*comment3*/; // issue #996 + + const char *t9 = /*comment4*/ "hello" + /*comment5*/ " world" /*comment6*/; + + const char *t10 = "hello" + "Mary" + "Lou"; +} + +void test4() +{ + auto txt = R"( + Hello World! + )"; +} + +// issue #2197 +namespace Test { + class Test { + private: + bool import = false; + } +} + +// issue #2192 +void test5() +{ + assert(module != nullptr); + for (int module=0; module<10; module++) {} + char modules[]= {} + for (auto module : modules) {} +} + +/* EOF */ diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.h b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.h new file mode 100644 index 0000000..e5308b1 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/highlighter.h @@ -0,0 +1,7 @@ +/* + Header +*/ + +int h1 = 0; + +/* EOF */ diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER1.hh new file mode 100644 index 0000000..a876606 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER1.hh @@ -0,0 +1 @@ +#define HEADER1 void header1(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER2.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER2.hh new file mode 100644 index 0000000..8083116 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/HEADER2.hh @@ -0,0 +1 @@ +#define HEADER2 void header2(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/bar.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/bar.hh new file mode 100644 index 0000000..77383f9 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/bar.hh @@ -0,0 +1 @@ +#define BAR void bar(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include1.hh new file mode 100644 index 0000000..c84facd --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include1.hh @@ -0,0 +1 @@ +#define INCLUDE1 void include1(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include2.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include2.hh new file mode 100644 index 0000000..027a6aa --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include2.hh @@ -0,0 +1 @@ +#define INCLUDE2 void include2(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include3.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include3.hh new file mode 100644 index 0000000..6427582 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/include3.hh @@ -0,0 +1 @@ +#define INCLUDE3 void include3(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/subfolder/include4.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/subfolder/include4.hh new file mode 100644 index 0000000..fb17444 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/subfolder/include4.hh @@ -0,0 +1 @@ +#define INCLUDE4 void include4(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/widget.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/widget.hh new file mode 100644 index 0000000..841a85c --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include/widget.hh @@ -0,0 +1 @@ +#define WIDGET void widget(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/include_snd_1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/include_snd_1.hh new file mode 100644 index 0000000..1a8dae1 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/include_snd_1.hh @@ -0,0 +1 @@ +#include "subfolder/include_snd_subfolder_1.hh" diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/subfolder/include_snd_subfolder_1.hh b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/subfolder/include_snd_subfolder_1.hh new file mode 100644 index 0000000..71e266e --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/include_snd/subfolder/include_snd_subfolder_1.hh @@ -0,0 +1 @@ +#define INCLUDE_SND_SUBFOLDER_1 void include_snd_subfolder_1(){} diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/sonar-project.properties b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/sonar-project.properties new file mode 100644 index 0000000..a05706d --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/sonar-project.properties @@ -0,0 +1,14 @@ +# required metadata +sonar.projectKey=TEST_include_directories +sonar.projectName=TEST_include_directories +sonar.projectVersion=0.0.1 + + +# disable xml +sonar.xml.file.suffixes=.disable-xml + +# path to source directories (required) +sonar.sources=src + +sonar.cxx.includeDirectories=include + diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/src/main.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/src/main.cc new file mode 100644 index 0000000..7710602 --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/include-directories-project/src/main.cc @@ -0,0 +1,29 @@ +// 1. include via a relpath with a backstep +#include "../include/include1.hh" +// 2. include via a filename from the include root +#include "include2.hh" +// 3. the same, but using <> +#include +// 4. include via a path relative to the include root +#include "subfolder/include4.hh" +// 5. indirect include of ../include_trd/something +#include "../include_snd/include_snd_1.hh" +// 6. macro replacement doesnt take place in <> and "" form of the include statement +#define HEADER FOO +#include "HEADER1.hh" +#include +// 7. ... but it does in the 'free' form of the include statement +#define LOCATION "bar.hh" +#include LOCATION +#define MACRO(p) p +#include MACRO("widget.hh") + +INCLUDE1 +INCLUDE2 +INCLUDE3 +INCLUDE4 +INCLUDE_SND_SUBFOLDER_1 +HEADER1 +HEADER2 +BAR //void bar(){} +WIDGET //void widget(){} \ No newline at end of file diff --git a/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/ncloc.cc b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/ncloc.cc new file mode 100644 index 0000000..83768cc --- /dev/null +++ b/sonar-keyware-plugins-cxx/src/test/resources/com/keyware/sonar/cxx/ncloc.cc @@ -0,0 +1,129 @@ +/* + Header +*/ + +#include "ncloc.h" + +// comment +void func1() +{ + h1 = 0; +} + +/* comment */ +void func2() +{ + const char* txt = + "Hello " + " World!"; +} + +void func3( + int a, + int b + ) +{ + return a + b; +} + +#define NUMBER 10 + +void func4() +{ + // comment + for(int iii=0; iii \ No newline at end of file