@@ -10,15 +10,14 @@ import core.Flags.*
1010import core .Contexts .{Context , ctx , inContext }
1111import core .DenotTransformers .IdentityDenotTransformer
1212import core .Symbols .{defn , Symbol }
13- import core .Decorators .{toTermName , i }
1413import core .Constants .Constant
1514import core .NameOps .isContextFunction
1615import core .Types .*
1716import typer .LiftCoverage
1817import util .{SourcePosition , Property }
1918import util .Spans .Span
2019import coverage .*
21- import localopt .StringInterpolatorOpt . isCompilerIntrinsic
20+ import localopt .StringInterpolatorOpt
2221
2322/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
2423 * ("instruments" the source code).
@@ -62,11 +61,21 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6261 /** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */
6362 private class CoverageTransformer extends Transformer :
6463 override def transform (tree : Tree )(using Context ): Tree =
64+ import scala .util .chaining .scalaUtilChainingOps
6565 inContext(transformCtx(tree)) { // necessary to position inlined code properly
6666 tree match
6767 // simple cases
6868 case tree : (Import | Export | Literal | This | Super | New ) => tree
69- case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ...
69+ case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident (referring to a type), TypeTree, ...
70+
71+ // identifier
72+ case tree : Ident =>
73+ val sym = tree.symbol
74+ if canInstrumentParameterless(sym) then
75+ // call to a local parameterless method f
76+ instrument(tree)
77+ else
78+ tree
7079
7180 // branches
7281 case tree : If =>
@@ -82,20 +91,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
8291 finalizer = instrument(transform(tree.finalizer), branch = true )
8392 )
8493
85- // a.f(args)
86- case tree @ Apply (fun : Select , args) =>
87- // don't transform the first Select, but do transform `a.b` in `a.b.f(args)`
88- val transformedFun = cpy.Select (fun)(transform(fun.qualifier), fun.name)
89- if canInstrumentApply(tree) then
90- if needsLift(tree) then
91- val transformed = cpy.Apply (tree)(transformedFun, args) // args will be transformed in instrumentLifted
92- instrumentLifted(transformed)
93- else
94- val transformed = transformApply(tree, transformedFun)
95- instrument(transformed)
96- else
97- transformApply(tree, transformedFun)
98-
9994 // f(args)
10095 case tree : Apply =>
10196 if canInstrumentApply(tree) then
@@ -106,24 +101,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
106101 else
107102 transformApply(tree)
108103
109- // (f(x) )[args]
110- case TypeApply (fun : Apply , args) =>
104+ // (fun )[args]
105+ case TypeApply (fun, args) =>
111106 cpy.TypeApply (tree)(transform(fun), args)
112107
113108 // a.b
114109 case Select (qual, name) =>
115- if qual.symbol.exists && qual.symbol.is( JavaDefined ) then
116- // Java class can't be used as a value, we can't instrument the
117- // qualifier ({<Probe>;System}.xyz() is not possible !) instrument it
118- // as it is
119- instrument(tree )
110+ val transformed = cpy. Select (tree)(transform(qual), name)
111+ val sym = tree.symbol
112+ if canInstrumentParameterless(sym) then
113+ // call to a parameterless method
114+ instrument(transformed )
120115 else
121- val transformed = cpy.Select (tree)(transform(qual), name)
122- if transformed.qualifier.isDef then
123- // instrument calls to methods without parameter list
124- instrument(transformed)
125- else
126- transformed
116+ transformed
127117
128118 case tree : CaseDef => instrumentCaseDef(tree)
129119 case tree : ValDef =>
@@ -142,7 +132,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
142132 val rhs = transform(tree.rhs)
143133 val finalRhs =
144134 if canInstrumentDefDef(tree) then
145- // Ensure that the rhs is always instrumented, if possible
135+ // Ensure that the rhs is always instrumented, if possible.
136+ // This is useful because methods can be stored and called later, or called by reflection,
137+ // and if the rhs is too simple to be instrumented (like `def f = this`), the method won't show up as covered.
146138 instrumentBody(tree, rhs)
147139 else
148140 rhs
@@ -162,7 +154,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
162154 }
163155
164156 /** Lifts and instruments an application.
165- * Note that if only one arg needs to be lifted, we just lift everything.
157+ * Note that if only one arg needs to be lifted, we just lift everything (see LiftCoverage) .
166158 */
167159 private def instrumentLifted (tree : Apply )(using Context ) =
168160 // lifting
@@ -178,10 +170,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
178170 )
179171
180172 private inline def transformApply (tree : Apply )(using Context ): Apply =
181- transformApply(tree, transform(tree.fun))
182-
183- private inline def transformApply (tree : Apply , transformedFun : Tree )(using Context ): Apply =
184- cpy.Apply (tree)(transformedFun, transform(tree.args))
173+ cpy.Apply (tree)(transform(tree.fun), transform(tree.args))
185174
186175 private inline def instrumentCases (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
187176 cases.map(instrumentCaseDef)
@@ -292,7 +281,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
292281 * they shouldn't be lifted.
293282 */
294283 val sym = fun.symbol
295- sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
284+ sym.exists && (isShortCircuitedOp(sym) || StringInterpolatorOpt . isCompilerIntrinsic(sym))
296285 end
297286
298287 val fun = tree.fun
@@ -312,7 +301,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
312301
313302 /** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */
314303 private def canInstrumentApply (tree : Apply )(using Context ): Boolean =
315- ! tree.symbol.isOneOf(Synthetic | Artifact ) && // no need to instrument synthetic apply
304+ val sym = tree.symbol
305+ ! sym.isOneOf(Synthetic | Artifact ) && // no need to instrument synthetic apply
306+ ! isCompilerIntrinsicMethod(sym) &&
316307 (tree.typeOpt match
317308 case AppliedType (tycon : NamedType , _) =>
318309 /* If the last expression in a block is a context function, we'll try to
@@ -339,6 +330,24 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
339330 true
340331 )
341332
333+ /** Is this the symbol of a parameterless method that we can instrument?
334+ * Note: it is crucial that `asInstanceOf` and `isInstanceOf`, among others,
335+ * do NOT get instrumented, because that would generate invalid code and crash
336+ * in post-erasure checking.
337+ */
338+ private def canInstrumentParameterless (sym : Symbol )(using Context ): Boolean =
339+ sym.is(Method , butNot = Synthetic | Artifact ) &&
340+ sym.info.isParameterless &&
341+ ! isCompilerIntrinsicMethod(sym)
342+
343+ /** Does sym refer to a "compiler intrinsic" method, which only exist during compilation,
344+ * like Any.isInstanceOf?
345+ * If this returns true, the call souldn't be instrumented.
346+ */
347+ private def isCompilerIntrinsicMethod (sym : Symbol )(using Context ): Boolean =
348+ val owner = sym.maybeOwner
349+ owner.eq(defn.AnyClass ) || owner.isPrimitiveValueClass
350+
342351object InstrumentCoverage :
343352 val name : String = " instrumentCoverage"
344353 val description : String = " instrument code for coverage checking"
0 commit comments