parent
02d782f556
commit
8d3f922056
@ -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(); |
||||
|
||||
} |
@ -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<Checks<SquidAstVisitor<Grammar>>> 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<Class> checkClass) { |
||||
checksByRepository.add(checkFactory |
||||
.<SquidAstVisitor<Grammar>>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<SquidAstVisitor<Grammar>> all() { |
||||
var allVisitors = new ArrayList<SquidAstVisitor<Grammar>>(); |
||||
|
||||
for (var checks : checksByRepository) { |
||||
allVisitors.addAll(checks.all()); |
||||
} |
||||
|
||||
return allVisitors; |
||||
} |
||||
|
||||
@CheckForNull |
||||
public RuleKey ruleKey(SquidAstVisitor<Grammar> check) { |
||||
for (var checks : checksByRepository) { |
||||
RuleKey ruleKey = checks.ruleKey(check); |
||||
if (ruleKey != null) { |
||||
return ruleKey; |
||||
} |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public Set<Checks<SquidAstVisitor<Grammar>>> getChecks() { |
||||
return new HashSet<>(checksByRepository); |
||||
} |
||||
|
||||
} |
@ -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<PropertyDefinition> 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; |
||||
} |
||||
|
||||
} |
@ -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<Object>(); |
||||
|
||||
// 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<Object> getSensorsImpl() { |
||||
var l = new ArrayList<Object>(); |
||||
|
||||
// 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(); |
||||
} |
||||
|
||||
} |
@ -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(); |
||||
} |
||||
|
||||
} |
@ -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<String> ruleKeys; |
||||
} |
||||
|
||||
} |
@ -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<PropertyDefinition> 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<SquidAstVisitor<Grammar>>(); |
||||
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<InputFile> inputFiles = getInputFiles(context, squidConfig); |
||||
scanner.scanInputFiles(inputFiles); |
||||
|
||||
Collection<SourceCode> 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<String> 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<File> 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<InputFile> getInputFiles(SensorContext context, CxxSquidConfiguration squidConfig) { |
||||
Iterable<InputFile> 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<SourceCode> 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<Grammar>) 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<Integer> linesOfCode = (List<Integer>) 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<Integer> executableLines = (List<Integer>) 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<CxxCpdVisitor.CpdToken> data = (List<CxxCpdVisitor.CpdToken>) 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<CxxHighlighterVisitor.Highlight> data = (List<CxxHighlighterVisitor.Highlight>) 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 <T extends Serializable> void saveMetric(InputFile file, Metric<T> metric, T value) { |
||||
context.<T>newMeasure() |
||||
.withValue(value) |
||||
.forMetric(metric) |
||||
.on(file) |
||||
.save(); |
||||
} |
||||
} |
@ -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<String, String> ALL_REMOVED_PROPERTIES = initRemovedProperties(); |
||||
private final AnalysisWarnings analysisWarnings; |
||||
|
||||
public DroppedPropertiesSensor(AnalysisWarnings analysisWarnings) { |
||||
this.analysisWarnings = analysisWarnings; |
||||
} |
||||
|
||||
private static Map<String, String> initRemovedProperties() { |
||||
var map = new HashMap<String, String>(); |
||||
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); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
} |
@ -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<Grammar> { |
||||
|
||||
@RuleProperty( |
||||
key = "customParam", |
||||
description = "Custom parameter", |
||||
defaultValue = "value") |
||||
public String customParam = "value"; |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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<Grammar> 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<Grammar> 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<Grammar> check(CxxChecks cxxChecks, String repository, String rule) { |
||||
RuleKey key = RuleKey.of(repository, rule); |
||||
|
||||
SquidAstVisitor<Grammar> 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<Grammar> { |
||||
} |
||||
|
||||
@Rule(key = CUSTOM_RULE_KEY, name = "This is the custom rule", description = "desc") |
||||
public static class MyCustomRule extends SquidCheck<Grammar> { |
||||
} |
||||
|
||||
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; |
||||
} |
||||
} |
||||
|
||||
} |
@ -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<Integer> 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<Integer> executableLines = new HashSet<>(); |
||||
public final Set<Integer> 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() { |
||||
} |
||||
} |
||||
|
||||
} |
@ -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<TypeOfText> 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); |
||||
} |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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); |
||||
} |
||||
|
||||
} |
@ -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<BuiltInQualityProfilesDefinition.BuiltInActiveRule> activeRules = profile.rules(); |
||||
assertThat(activeRules.size()).as("Expected number of rules in profile").isNotNegative(); |
||||
} |
||||
|
||||
} |
@ -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<TokensLine> 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); |
||||
} |
||||
|
||||
} |
@ -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<String> 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<String> 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<String> 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); |
||||
} |
||||
|
||||
} |
@ -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
|
||||
} |
||||
|
||||
} |
@ -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=. |
||||
|
@ -0,0 +1,5 @@ |
||||
#ifndef TEST1 |
||||
#define TEST1 |
||||
#include "test2.hh" |
||||
void test1(); |
||||
#endif |
@ -0,0 +1,5 @@ |
||||
#ifndef TEST2 |
||||
#define TEST2 |
||||
#include "test1.hh" |
||||
void test2(); |
||||
#endif |
@ -0,0 +1,5 @@ |
||||
#ifndef TEST1 |
||||
#define TEST1 |
||||
#include "../Package2/test2.hh" |
||||
void test1(); |
||||
#endif |
@ -0,0 +1,4 @@ |
||||
#ifndef TEST2 |
||||
#define TEST2 |
||||
void test2(); |
||||
#endif |
@ -0,0 +1,6 @@ |
||||
#ifndef TEST3 |
||||
#define TEST3 |
||||
#include "../Package1/test1.hh" |
||||
#include "test2.hh" |
||||
void test3(); |
||||
#endif |
@ -0,0 +1,5 @@ |
||||
#ifndef TEST4 |
||||
#define TEST4 |
||||
#include "../Package1/test1.hh" |
||||
void test4(); |
||||
#endif |
@ -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=. |
||||
|
@ -0,0 +1,91 @@ |
||||
/* Fudge unix isatty and fileno for RISCOS */ |
||||
|
||||
#include "unixstuff.h" |
||||
#include <math.h> |
||||
#include <time.h> |
||||
#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;
|
||||
// }
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
@ -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=. |
@ -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
|
@ -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=. |
@ -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(); |
||||
}; |
@ -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 |
@ -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=. |
@ -0,0 +1 @@ |
||||
MACRO |
@ -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 |
@ -0,0 +1 @@ |
||||
class TestClass1{}; |
@ -0,0 +1,2 @@ |
||||
class TestClass5_A{}; |
||||
class TestClass5_B{}; |
@ -0,0 +1,4 @@ |
||||
class TestClass6{ |
||||
void test_foo(); |
||||
void test_bar(); |
||||
}; |
@ -0,0 +1,2 @@ |
||||
#include "Test6.hh" |
||||
void TestClass6::test_foo(){}; |
@ -0,0 +1,2 @@ |
||||
#include "Test6.hh" |
||||
void TestClass6::test_bar(){}; |
@ -0,0 +1,4 @@ |
||||
namespace my_test_suite { |
||||
struct my_test { void test_method(); }; |
||||
void my_test::test_method() {} |
||||
} |
@ -0,0 +1 @@ |
||||
class TestClass2{}; |
@ -0,0 +1,3 @@ |
||||
namespace ns{ |
||||
class TestClass3{}; |
||||
}; |
@ -0,0 +1,2 @@ |
||||
#include "Test4.hh" |
||||
void ns::TestClass4::test1(){} |
@ -0,0 +1,5 @@ |
||||
namespace ns{ |
||||
class TestClass4{ |
||||
void test1(); |
||||
}; |
||||
}; |
@ -0,0 +1,12 @@ |
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<testsuites tests="1" failures="0" disabled="0" errors="0" time="0.1" name="AllTests"> |
||||
<testsuite name="lalal" tests="1" failures="0" disabled="0" errors="0" time="0.1"> |
||||
<testcase name="testcase1" status="run" time="0.1" classname="TestClass1" /> |
||||
<testcase name="testcase2" status="run" time="0.1" classname="TestClass2" /> |
||||
<testcase name="testcase3" status="run" time="0.1" classname="TestClass3" /> |
||||
<testcase name="testcase4" status="run" time="0.1" classname="TestClass4" /> |
||||
<testcase name="testcase5_a" status="run" time="0.1" classname="TestClass5_A" /> |
||||
<testcase name="testcase5_b" status="run" time="0.1" classname="TestClass5_B" /> |
||||
<testcase name="testcase6" status="run" time="0.1" classname="TestClass6" /> |
||||
</testsuite> |
||||
</testsuites> |
@ -0,0 +1 @@ |
||||
#define MACRO1(a) if(a); |
@ -0,0 +1 @@ |
||||
#define MACRO2(a) void func() { MACRO1(a) } |
@ -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 |
@ -0,0 +1 @@ |
||||
MACRO2(2) |
@ -0,0 +1 @@ |
||||
MACRO2(1) |
@ -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 */ |
@ -0,0 +1,7 @@ |
||||
/*
|
||||
Header |
||||
*/ |
||||
|
||||
int h1 = 0; |
||||
|
||||
/* EOF */ |
@ -0,0 +1 @@ |
||||
#define HEADER1 void header1(){} |
@ -0,0 +1 @@ |
||||
#define HEADER2 void header2(){} |
@ -0,0 +1 @@ |
||||
#define BAR void bar(){} |
@ -0,0 +1 @@ |
||||
#define INCLUDE1 void include1(){} |
@ -0,0 +1 @@ |
||||
#define INCLUDE2 void include2(){} |
@ -0,0 +1 @@ |
||||
#define INCLUDE3 void include3(){} |
@ -0,0 +1 @@ |
||||
#define INCLUDE4 void include4(){} |
@ -0,0 +1 @@ |
||||
#define WIDGET void widget(){} |
@ -0,0 +1 @@ |
||||
#include "subfolder/include_snd_subfolder_1.hh" |
@ -0,0 +1 @@ |
||||
#define INCLUDE_SND_SUBFOLDER_1 void include_snd_subfolder_1(){} |
@ -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 |
||||
|
@ -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 <include3.hh> |
||||
// 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 <HEADER2.hh> |
||||
// 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(){}
|
@ -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<NUMBER; ++iii) { |
||||
h1 += iii; // comment
|
||||
} |
||||
} |
||||
|
||||
#define ADD(a, b) \ |
||||
((a) + (b)) |
||||
|
||||
void func5() |
||||
{ |
||||
int a=1, b=2; |
||||
int sum = ADD(a, b); |
||||
} |
||||
|
||||
// declaration
|
||||
void func6(); |
||||
|
||||
void func7(bool flag) |
||||
{ |
||||
int iii = 0; |
||||
|
||||
if (flag) { |
||||
iii = 1; |
||||
} |
||||
else { |
||||
iii = 0; |
||||
} |
||||
} |
||||
|
||||
int func8(int p) |
||||
{ |
||||
int r; |
||||
|
||||
switch (p) { |
||||
case 0: |
||||
r = 100; |
||||
break; |
||||
case 1: |
||||
r = 200; |
||||
break; |
||||
default: |
||||
r = 300; |
||||
break; |
||||
} |
||||
|
||||
return r; |
||||
} |
||||
|
||||
void func9() |
||||
{ |
||||
int *p; |
||||
|
||||
try { |
||||
p = new int[10]; |
||||
} |
||||
catch (...) { |
||||
p = nullptr; |
||||
} |
||||
|
||||
} |
||||
|
||||
void func10() |
||||
{ |
||||
// comment
|
||||
for (int iii = 0; |
||||
iii < NUMBER; |
||||
++iii) |
||||
{ |
||||
h1 += iii; // comment
|
||||
} |
||||
} |
||||
|
||||
// inline
|
||||
class MyClass { |
||||
public: |
||||
MyClass(); |
||||
MyClass(const MyClass&) = delete; |
||||
MyClass& operator=(const MyClass&) = default; |
||||
|
||||
int method1(); |
||||
|
||||
int method2() |
||||
{ |
||||
// comment
|
||||
for(int iii=0; iii<NUMBER; ++iii) { |
||||
h1 += iii; // comment
|
||||
} |
||||
} |
||||
}; |
||||
|
||||
constexpr int factorial(int n) |
||||
{ |
||||
return n <= 1 ? 1 : (n * factorial(n - 1)); |
||||
} |
||||
|
||||
/* EOF */ |
@ -0,0 +1,7 @@ |
||||
/*
|
||||
Header |
||||
*/ |
||||
|
||||
int h1 = 0; |
||||
|
||||
/* EOF */ |
@ -0,0 +1 @@ |
||||
<c++ source> |
Loading…
Reference in new issue