@@ -10,15 +10,14 @@ import core.Flags.*
10
10
import core .Contexts .{Context , ctx , inContext }
11
11
import core .DenotTransformers .IdentityDenotTransformer
12
12
import core .Symbols .{defn , Symbol }
13
- import core .Decorators .{toTermName , i }
14
13
import core .Constants .Constant
15
14
import core .NameOps .isContextFunction
16
15
import core .Types .*
17
16
import typer .LiftCoverage
18
17
import util .{SourcePosition , Property }
19
18
import util .Spans .Span
20
19
import coverage .*
21
- import localopt .StringInterpolatorOpt . isCompilerIntrinsic
20
+ import localopt .StringInterpolatorOpt
22
21
23
22
/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
24
23
* ("instruments" the source code).
@@ -62,11 +61,21 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
62
61
/** Transforms trees to insert calls to Invoker.invoked to compute the coverage when the code is called */
63
62
private class CoverageTransformer extends Transformer :
64
63
override def transform (tree : Tree )(using Context ): Tree =
64
+ import scala .util .chaining .scalaUtilChainingOps
65
65
inContext(transformCtx(tree)) { // necessary to position inlined code properly
66
66
tree match
67
67
// simple cases
68
68
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
70
79
71
80
// branches
72
81
case tree : If =>
@@ -82,20 +91,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
82
91
finalizer = instrument(transform(tree.finalizer), branch = true )
83
92
)
84
93
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
-
99
94
// f(args)
100
95
case tree : Apply =>
101
96
if canInstrumentApply(tree) then
@@ -106,24 +101,19 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
106
101
else
107
102
transformApply(tree)
108
103
109
- // (f(x) )[args]
110
- case TypeApply (fun : Apply , args) =>
104
+ // (fun )[args]
105
+ case TypeApply (fun, args) =>
111
106
cpy.TypeApply (tree)(transform(fun), args)
112
107
113
108
// a.b
114
109
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 )
120
115
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
127
117
128
118
case tree : CaseDef => instrumentCaseDef(tree)
129
119
case tree : ValDef =>
@@ -142,7 +132,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
142
132
val rhs = transform(tree.rhs)
143
133
val finalRhs =
144
134
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.
146
138
instrumentBody(tree, rhs)
147
139
else
148
140
rhs
@@ -162,7 +154,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
162
154
}
163
155
164
156
/** 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) .
166
158
*/
167
159
private def instrumentLifted (tree : Apply )(using Context ) =
168
160
// lifting
@@ -178,10 +170,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
178
170
)
179
171
180
172
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))
185
174
186
175
private inline def instrumentCases (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
187
176
cases.map(instrumentCaseDef)
@@ -292,7 +281,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
292
281
* they shouldn't be lifted.
293
282
*/
294
283
val sym = fun.symbol
295
- sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
284
+ sym.exists && (isShortCircuitedOp(sym) || StringInterpolatorOpt . isCompilerIntrinsic(sym))
296
285
end
297
286
298
287
val fun = tree.fun
@@ -312,7 +301,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
312
301
313
302
/** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */
314
303
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) &&
316
307
(tree.typeOpt match
317
308
case AppliedType (tycon : NamedType , _) =>
318
309
/* 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:
339
330
true
340
331
)
341
332
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
+
342
351
object InstrumentCoverage :
343
352
val name : String = " instrumentCoverage"
344
353
val description : String = " instrument code for coverage checking"
0 commit comments