Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
644e00b
OC-126: a list of things we need to test
marek-parfianowicz Nov 5, 2023
f73046e
OC-126: more test samples
marek-parfianowicz Nov 5, 2023
25a6c4a
OC-126: more test cases for switches
marek-parfianowicz Dec 6, 2023
d85e805
OC-126: more test cases for text blocks
marek-parfianowicz Dec 6, 2023
61c6662
OC-126: correct Java15TextBlockInvalid test
marek-parfianowicz Dec 12, 2023
0c298e2
OC-126: helper methods in JavaSyntaxCompilationTestBase, test cases f…
marek-parfianowicz Dec 27, 2023
3dcfe7e
OC-126: test cases for switch expressions when lambda expressions can…
marek-parfianowicz Dec 27, 2023
8702d61
OC-126: quote code matched in regular expressions
marek-parfianowicz Dec 27, 2023
7994476
OC-126: test cases for switch expressions with blocks returning value…
marek-parfianowicz Dec 27, 2023
570a561
OC-126: test cases for switch expressions with multiple values in a c…
marek-parfianowicz Dec 27, 2023
04663ff
OC-126: test cases for switch expressions used in different contexts,…
marek-parfianowicz Jan 2, 2024
a1a4d34
OC-126: rename existing rules related with switch statements, add doc…
marek-parfianowicz Jan 3, 2024
44b8fcd
OC-126: a draft of the switch expression grammar
marek-parfianowicz Jan 3, 2024
c02ef05
OC-126: java.g - handle 'yield x' statement; correct lambdaCase gramm…
marek-parfianowicz Jan 8, 2024
6f48dbf
OC-126: refactor grammar related with two forms of switch blocks; bot…
marek-parfianowicz Jan 16, 2024
c0c0fa6
OC-126: new LanguageFeature.SWITCH_EXPRESSIONS added for Java 14+; th…
marek-parfianowicz Jan 17, 2024
e91b382
OC-126: created code entry/exit emitters for case expressions; value-…
marek-parfianowicz Jan 17, 2024
76ccc0f
OC-126: wire code emitters to grammar rules (DRAFT)
marek-parfianowicz Jan 17, 2024
956e6b1
OC-126: fix tests
marek-parfianowicz Jan 18, 2024
de4c1fa
OC-126: fix tests
marek-parfianowicz Jan 18, 2024
698c123
OC-126: java.g - allow to use expressions in 'case' label and not jus…
marek-parfianowicz Jan 19, 2024
5db65cc
OC-126: create a grammar to handle 'constant expressions' (see java l…
marek-parfianowicz Jan 20, 2024
695a671
OC-126: cyclomatic complexity everywhere!! because it's allowed to us…
marek-parfianowicz Jan 21, 2024
5d7c41d
OC-126: split humongous InstrumentationTest into InstrumentationTestB…
marek-parfianowicz Jan 22, 2024
d723e10
OC-126: correct cyclomatic complexity calculation for switch statemen…
marek-parfianowicz Jan 30, 2024
cc54040
OC-126: test that cyclomatic complexity for hidden paths in switch st…
marek-parfianowicz Jan 30, 2024
50e33fb
OC-229: take into account potential non-zero cyclomatic complexity of…
marek-parfianowicz Jan 31, 2024
6db55ef
OC-126: test for propagation of cyclomatic complexity through method …
marek-parfianowicz Jan 31, 2024
6b95bef
OC-126: test for propagation of cyclomatic complexity for argument li…
marek-parfianowicz Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,7 @@ public enum LanguageFeature {
/** Multi-line text blocks in """...""" */
TEXT_BLOCKS,
/** Record classes and compact canonical constructors */
RECORDS
RECORDS,
/** Switch treated as expressions */
SWITCH_EXPRESSIONS
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import static com.atlassian.clover.cfg.instr.java.LanguageFeature.LAMBDA;
import static com.atlassian.clover.cfg.instr.java.LanguageFeature.MODULES;
import static com.atlassian.clover.cfg.instr.java.LanguageFeature.RECORDS;
import static com.atlassian.clover.cfg.instr.java.LanguageFeature.SWITCH_EXPRESSIONS;
import static com.atlassian.clover.cfg.instr.java.LanguageFeature.TEXT_BLOCKS;
import static org.openclover.util.Sets.newHashSet;

Expand All @@ -24,10 +25,10 @@ public enum SourceLevel {
JAVA_11("11", newHashSet("1.11", "11"), newHashSet(LAMBDA, MODULES)),
JAVA_12("12", newHashSet("12"), newHashSet(LAMBDA, MODULES)),
JAVA_13("13", newHashSet("13"), newHashSet(LAMBDA, MODULES)),
JAVA_14("14", newHashSet("14"), newHashSet(LAMBDA, MODULES)),
JAVA_15("15", newHashSet("15"), newHashSet(LAMBDA, MODULES, TEXT_BLOCKS)),
JAVA_16("16", newHashSet("16"), newHashSet(LAMBDA, MODULES, TEXT_BLOCKS, RECORDS)),
JAVA_17("17", newHashSet("17"), newHashSet(LAMBDA, MODULES, TEXT_BLOCKS, RECORDS));
JAVA_14("14", newHashSet("14"), newHashSet(LAMBDA, MODULES, SWITCH_EXPRESSIONS)),
JAVA_15("15", newHashSet("15"), newHashSet(LAMBDA, MODULES, SWITCH_EXPRESSIONS, TEXT_BLOCKS)),
JAVA_16("16", newHashSet("16"), newHashSet(LAMBDA, MODULES, SWITCH_EXPRESSIONS, TEXT_BLOCKS, RECORDS)),
JAVA_17("17", newHashSet("17"), newHashSet(LAMBDA, MODULES, SWITCH_EXPRESSIONS, TEXT_BLOCKS, RECORDS));

private static final Set<String> unsupportedSourceLevels =
newHashSet("1.0", "1.1", "1.2", "1.3", "1.4", "1.5", "5", "1.6", "6");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package com.atlassian.clover.instr.java;

import com.atlassian.clover.context.ContextSet;
import com.atlassian.clover.context.NamedContext;
import com.atlassian.clover.registry.FixedSourceRegion;
import com.atlassian.clover.registry.entities.FullStatementInfo;
import com.atlassian.clover.spi.lang.LanguageConstruct;

/**
* A code emitter for lambda case expressions. Rewrites an expression like:
* <pre>
* case 0 -> "abc";
* ^ ^
* </pre>
* into:
* <pre>
* case 0 -> caseInc(123, () -> "abc";
* ~~~~~~~~~~~~~~~~~~ ^ ^
* </pre>
* <p>
* {@link CaseExpressionExitEmitter}
*/
public class CaseExpressionEntryEmitter extends Emitter {

/**
* A line number where expression ends (position of a semicolon)
*/
private final int endLine;

/**
* A column number where expression ends (position of a semicolon)
*/
private final int endCol;

/**
* A complexity of the entire expression.
* It can be non-0 if a ternary expression or another switch expression is present in it.
*/
private final int complexity;

/**
* A statement registered for this case expression.
*/
FullStatementInfo stmtInfo;

public CaseExpressionEntryEmitter(ContextSet context, int line, int column, int endLine, int endCol, int complexity) {
super(context, line, column);
this.endLine = endLine;
this.endCol = endCol;
this.complexity = complexity;
}

@Override
protected void init(InstrumentationState state) {
if (state.isInstrEnabled()) {
state.setDirty();

// record the statement
stmtInfo = state.getSession().addStatement(
getElementContext(),
new FixedSourceRegion(getLine(), getColumn(), endLine, endCol),
complexity,
LanguageConstruct.Builtin.STATEMENT);

boolean classInstrStrategy = state.getCfg().isClassInstrStrategy();
if (classInstrStrategy) {
// emit text like [__CLRxxxxxxxx.caseInc(123,()->]
final String recorderBase = state.getRecorderPrefix().substring(0, state.getRecorderPrefix().lastIndexOf('.'));
final StringBuilder instr = new StringBuilder();
instr.append(recorderBase);
instr.append(".");
instr.append(RecorderInstrEmitter.CASE_INC_METHOD);
instr.append("(");
instr.append(stmtInfo.getDataIndex());
// add a comma and lambda call, because we'll have original case expression wrapped in a lambda as a second argument
instr.append(",()->");
setInstr(instr.toString());
}
}
}

@Override
public void addContext(NamedContext ctx) {
super.addContext(ctx);
if (stmtInfo != null) {
stmtInfo.addContext(ctx);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.atlassian.clover.instr.java;

/**
* A code emitter for lambda case expressions. Rewrites an expression like:
* <pre>
* case 0 -> "abc";
* ^ ^
* </pre>
* into:
* <pre>
* case 0 -> "abc");
* ^ ~^
* </pre>
*
* {@link CaseExpressionEntryEmitter}
*/
public class CaseExpressionExitEmitter extends Emitter {

private final CaseExpressionEntryEmitter entryEmitter;

public CaseExpressionExitEmitter(CaseExpressionEntryEmitter entryEmitter) {
this.entryEmitter = entryEmitter;
}

@Override
protected void init(InstrumentationState state) {
// we must close the wrapped expression only if the start was wrapped, ignoring
// any CLOVER:OFF inside (state.isInstrEnabled() check would be wrong)
if (entryEmitter.stmtInfo != null && state.getCfg().isClassInstrStrategy()) {
setInstr(")");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.atlassian.clover.instr.java;

import com.atlassian.clover.context.ContextSet;
import com.atlassian.clover.registry.FixedSourceRegion;
import com.atlassian.clover.registry.entities.FullStatementInfo;
import com.atlassian.clover.spi.lang.LanguageConstruct;

import static com.atlassian.clover.instr.Bindings.$CoverageRecorder$inc;

/**
* A code emitter for lambda case expressions with the {@code throw} keyword.
* Rewrites an expression like:
* <pre>
* case 0 -> throw new Exception();
* ^ ^
* </pre>
* into:
* <pre>
* case 0 -> { R.inc(); throw new Exception();
* ~~~~~~~~~~ ^ ^
* </pre>
*
* {@link CaseThrowExpressionExitEmitter}
*/
public class CaseThrowExpressionEntryEmitter extends Emitter {

/** A line number where expression ends (position of a semicolon) */
private final int endLine;

/** A column number where expression ends (position of a semicolon) */
private final int endCol;

/**
* A complexity of the entire expression.
* It can be non-0 if a ternary expression or another switch expression is present in it.
*/
private final int complexity;

/**
* A statement registered for this case throw expression.
*/
FullStatementInfo stmtInfo;

public CaseThrowExpressionEntryEmitter(ContextSet context, int line, int column, int endLine, int endCol, int complexity) {
super(context, line, column);
this.endLine = endLine;
this.endCol = endCol;
this.complexity = complexity;
}

@Override
protected void init(InstrumentationState state) {
if (state.isInstrEnabled()) {
state.setDirty();
// record the statement
stmtInfo =
state.getSession().addStatement(
getElementContext(),
new FixedSourceRegion(getLine(), getColumn(), endLine, endCol),
complexity,
LanguageConstruct.Builtin.STATEMENT);

if (state.getCfg().isClassInstrStrategy()) {
// emit text like [{__CLRxxxxxxxx.inc(123);]
final String instr = "{" +
$CoverageRecorder$inc(state.getRecorderPrefix(), Integer.toString(stmtInfo.getDataIndex())) +
";";
setInstr(instr);
}
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.atlassian.clover.instr.java;

/**
* A code emitter for lambda case expressions with the {@code throw} keyword.
* Rewrites an expression like:
* <pre>
* case 0 -> throw new Exception();
* ^ ^
* </pre>
* into:
* <pre>
* case 0 -> throw new Exception();}
* ^ ^~
* </pre>
*
* {@link CaseThrowExpressionEntryEmitter}
*/
public class CaseThrowExpressionExitEmitter extends Emitter {

private CaseThrowExpressionEntryEmitter entryEmitter;

public CaseThrowExpressionExitEmitter(CaseThrowExpressionEntryEmitter entryEmitter) {
this.entryEmitter = entryEmitter;
}

@Override
protected void init(InstrumentationState state) {
// we must close the wrapped expression only if the start was wrapped, ignoring
// any CLOVER:OFF inside (state.isInstrEnabled() check would be wrong)
if (entryEmitter.stmtInfo != null && state.getCfg().isClassInstrStrategy()) {
setInstr("}");
}
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.atlassian.clover.instr.java;

import com.atlassian.clover.context.ContextSet;

/**
* A helper class used in java.g
*/
class ContextSetAndComplexity {
private ContextSet context;
private int complexity;

public static ContextSetAndComplexity empty() {
return new ContextSetAndComplexity(null, 0);
}

public static ContextSetAndComplexity ofComplexity(int complexity) {
return new ContextSetAndComplexity(null, complexity);
}

public void setContext(ContextSet context) {
this.context = context;
}

public void addComplexity(int increment) {
complexity += increment;
}

public ContextSet getContext() {
return context;
}

public int getComplexity() {
return complexity;
}

private ContextSetAndComplexity(ContextSet context, int complexity) {
this.context = context;
this.complexity = complexity;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,10 @@
public class RecorderInstrEmitter extends Emitter {

static final String LAMBDA_INC_METHOD = "lambdaInc";
static final String CASE_INC_METHOD = "caseInc";

private static final String INCOMPATIBLE_MSG =
"[CLOVER] WARNING: The Clover version used in instrumentation does " +
"not match the runtime version. You need to run instrumented classes against the same version of Clover " +
"that you instrumented with.";
"[CLOVER] WARNING: The Clover version used in instrumentation shall match the runtime version.";
private static final String DEFAULT_CLASSNOTFOUND_MSG =
"[CLOVER] FATAL ERROR: Clover could not be initialised. Are you " +
"sure you have Clover in the runtime classpath?";
Expand Down Expand Up @@ -78,6 +77,7 @@ private boolean isJUnit5ParameterizedTest() {
private boolean shouldEmitWarningMethod;
private List<CloverProfile> profiles;
private boolean areLambdasSupported;
private boolean areSwitchExpressionsSupported;

public RecorderInstrEmitter(boolean isEnum) {
super();
Expand All @@ -97,6 +97,7 @@ public void init(InstrumentationState state) {
registryVersion = state.getSession().getVersion();
javaLangPrefix = state.getCfg().getJavaLangPrefix();
areLambdasSupported = state.getCfg().getSourceLevel().supportsFeature(LanguageFeature.LAMBDA);
areSwitchExpressionsSupported = state.getCfg().getSourceLevel().supportsFeature(LanguageFeature.SWITCH_EXPRESSIONS);
testClass = state.isDetectTests();
isSpockTestClass = state.isSpockTestClass();
isParameterizedJUnitTestClass = state.isParameterizedJUnitTestClass();
Expand Down Expand Up @@ -131,6 +132,12 @@ public String getInstr() {
instrString += generateLambdaIncMethod(recorderSuffix);
}

// add caseInc() wrappers for switch case written as expressions
if (areSwitchExpressionsSupported) {
instrString += generateCaseIncValueMethod(recorderSuffix);
instrString += generateCaseIncVoidMethod(recorderSuffix);
}

// static initialization block
instrString += "static{";

Expand Down Expand Up @@ -300,6 +307,38 @@ private String generateLambdaIncMethod(final String recorderSuffix) {
return str.toString();
}

/**
* Generates caseInc helper method to wrap case expressions returning values.
* <pre>
* public static <T> T caseInc(int i, java.util.function.Supplier<T> s) {
* inc(i);
* return s.get();
* }
* </pre>
* @param recorderSuffix name of the coverage recorder field
* @return String code of the method
*/
private String generateCaseIncValueMethod(String recorderSuffix) {
return "public static <T> T caseInc(int i,java.util.function.Supplier<T> s){" +
recorderSuffix + ".inc(i);return s.get();}";
}

/**
* Generates caseInc helper method to wrap case expressions returning values.
* <pre>
* public static void caseInc(int i, Runnable r) {
* inc(i);
* r.run();
* }
* </pre>
* @param recorderSuffix name of the coverage recorder field
* @return String code of the method
*/
private String generateCaseIncVoidMethod(String recorderSuffix) {
return "public static void caseInc(int i,Runnable r){" +
recorderSuffix + ".inc(i);r.run();}";
}

/**
* Return a string containing declaration of a field with a static array containing
* list of profiles. Example:
Expand Down
Loading