@@ -2,23 +2,21 @@ package dotty.tools.dotc
2
2
package transform
3
3
4
4
import java .io .File
5
- import java .util .concurrent .atomic .AtomicInteger
6
5
7
6
import ast .tpd .*
8
7
import collection .mutable
9
8
import core .Flags .*
10
9
import core .Contexts .{Context , ctx , inContext }
11
10
import core .DenotTransformers .IdentityDenotTransformer
12
11
import core .Symbols .{defn , Symbol }
13
- import core .Decorators .{toTermName , i }
14
12
import core .Constants .Constant
15
13
import core .NameOps .isContextFunction
16
14
import core .Types .*
15
+ import coverage .*
17
16
import typer .LiftCoverage
18
- import util .{ SourcePosition , Property }
17
+ import util .SourcePosition
19
18
import util .Spans .Span
20
- import coverage .*
21
- import localopt .StringInterpolatorOpt .isCompilerIntrinsic
19
+ import localopt .StringInterpolatorOpt
22
20
23
21
/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
24
22
* ("instruments" the source code).
@@ -44,7 +42,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
44
42
val outputPath = ctx.settings.coverageOutputDir.value
45
43
46
44
// Ensure the dir exists
47
- val dataDir = new File (outputPath)
45
+ val dataDir = File (outputPath)
48
46
val newlyCreated = dataDir.mkdirs()
49
47
50
48
if ! newlyCreated then
@@ -66,7 +64,16 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
66
64
tree match
67
65
// simple cases
68
66
case tree : (Import | Export | Literal | This | Super | New ) => tree
69
- case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident, TypTree, ...
67
+ case tree if tree.isEmpty || tree.isType => tree // empty Thicket, Ident (referring to a type), TypeTree, ...
68
+
69
+ // identifier
70
+ case tree : Ident =>
71
+ val sym = tree.symbol
72
+ if canInstrumentParameterless(sym) then
73
+ // call to a local parameterless method f
74
+ instrument(tree)
75
+ else
76
+ tree
70
77
71
78
// branches
72
79
case tree : If =>
@@ -82,20 +89,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
82
89
finalizer = instrument(transform(tree.finalizer), branch = true )
83
90
)
84
91
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
92
// f(args)
100
93
case tree : Apply =>
101
94
if canInstrumentApply(tree) then
@@ -106,24 +99,36 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
106
99
else
107
100
transformApply(tree)
108
101
109
- // (f(x))[args]
110
- case TypeApply (fun : Apply , args) =>
111
- cpy.TypeApply (tree)(transform(fun), args)
102
+ // (fun)[args]
103
+ case TypeApply (fun, args) =>
104
+ val tfun = transform(fun)
105
+ tfun match
106
+ case InstrumentCoverage .InstrumentedBlock (invokeCall, expr) =>
107
+ // expr[T] shouldn't be transformed to
108
+ // {invoked(...), expr}[T]
109
+ //
110
+ // but to
111
+ // {invoked(...), expr[T]}
112
+ //
113
+ // This is especially important for trees like (expr[T])(args),
114
+ // for which the wrong transformation crashes the compiler.
115
+ // See tests/coverage/pos/PolymorphicExtensions.scala
116
+ Block (
117
+ invokeCall :: Nil ,
118
+ cpy.TypeApply (tree)(expr, args)
119
+ )
120
+ case _ =>
121
+ cpy.TypeApply (tree)(tfun, args)
112
122
113
123
// a.b
114
124
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 )
125
+ val transformed = cpy. Select (tree)(transform(qual), name)
126
+ val sym = tree.symbol
127
+ if canInstrumentParameterless(sym) then
128
+ // call to a parameterless method
129
+ instrument(transformed )
120
130
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
131
+ transformed
127
132
128
133
case tree : CaseDef => instrumentCaseDef(tree)
129
134
case tree : ValDef =>
@@ -142,7 +147,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
142
147
val rhs = transform(tree.rhs)
143
148
val finalRhs =
144
149
if canInstrumentDefDef(tree) then
145
- // Ensure that the rhs is always instrumented, if possible
150
+ // Ensure that the rhs is always instrumented, if possible.
151
+ // This is useful because methods can be stored and called later, or called by reflection,
152
+ // and if the rhs is too simple to be instrumented (like `def f = this`), the method won't show up as covered.
146
153
instrumentBody(tree, rhs)
147
154
else
148
155
rhs
@@ -162,7 +169,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
162
169
}
163
170
164
171
/** Lifts and instruments an application.
165
- * Note that if only one arg needs to be lifted, we just lift everything.
172
+ * Note that if only one arg needs to be lifted, we just lift everything (see LiftCoverage) .
166
173
*/
167
174
private def instrumentLifted (tree : Apply )(using Context ) =
168
175
// lifting
@@ -178,10 +185,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
178
185
)
179
186
180
187
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))
188
+ cpy.Apply (tree)(transform(tree.fun), transform(tree.args))
185
189
186
190
private inline def instrumentCases (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
187
191
cases.map(instrumentCaseDef)
@@ -201,7 +205,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
201
205
private def recordStatement (tree : Tree , pos : SourcePosition , branch : Boolean )(using ctx : Context ): Int =
202
206
val id = statementId
203
207
statementId += 1
204
- val statement = new Statement (
208
+ val statement = Statement (
205
209
source = ctx.source.file.name,
206
210
location = Location (tree),
207
211
id = id,
@@ -255,12 +259,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
255
259
val statementId = recordStatement(parent, pos, false )
256
260
insertInvokeCall(body, pos, statementId)
257
261
258
- /** Returns the tree, prepended by a call to Invoker.invoker */
262
+ /** Returns the tree, prepended by a call to Invoker.invoked */
259
263
private def insertInvokeCall (tree : Tree , pos : SourcePosition , statementId : Int )(using Context ): Tree =
260
264
val callSpan = syntheticSpan(pos)
261
265
Block (invokeCall(statementId, callSpan) :: Nil , tree).withSpan(callSpan.union(tree.span))
262
266
263
- /** Generates Invoked .invoked(id, DIR) */
267
+ /** Generates Invoker .invoked(id, DIR) */
264
268
private def invokeCall (id : Int , span : Span )(using Context ): Tree =
265
269
val outputPath = ctx.settings.coverageOutputDir.value
266
270
ref(defn.InvokedMethodRef ).withSpan(span)
@@ -292,7 +296,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
292
296
* they shouldn't be lifted.
293
297
*/
294
298
val sym = fun.symbol
295
- sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
299
+ sym.exists && (isShortCircuitedOp(sym) || StringInterpolatorOpt . isCompilerIntrinsic(sym))
296
300
end
297
301
298
302
val fun = tree.fun
@@ -312,7 +316,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
312
316
313
317
/** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */
314
318
private def canInstrumentApply (tree : Apply )(using Context ): Boolean =
315
- ! tree.symbol.isOneOf(Synthetic | Artifact ) && // no need to instrument synthetic apply
319
+ val sym = tree.symbol
320
+ ! sym.isOneOf(Synthetic | Artifact ) && // no need to instrument synthetic apply
321
+ ! isCompilerIntrinsicMethod(sym) &&
316
322
(tree.typeOpt match
317
323
case AppliedType (tycon : NamedType , _) =>
318
324
/* If the last expression in a block is a context function, we'll try to
@@ -339,6 +345,40 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
339
345
true
340
346
)
341
347
348
+ /** Is this the symbol of a parameterless method that we can instrument?
349
+ * Note: it is crucial that `asInstanceOf` and `isInstanceOf`, among others,
350
+ * do NOT get instrumented, because that would generate invalid code and crash
351
+ * in post-erasure checking.
352
+ */
353
+ private def canInstrumentParameterless (sym : Symbol )(using Context ): Boolean =
354
+ sym.is(Method , butNot = Synthetic | Artifact ) &&
355
+ sym.info.isParameterless &&
356
+ ! isCompilerIntrinsicMethod(sym)
357
+
358
+ /** Does sym refer to a "compiler intrinsic" method, which only exist during compilation,
359
+ * like Any.isInstanceOf?
360
+ * If this returns true, the call souldn't be instrumented.
361
+ */
362
+ private def isCompilerIntrinsicMethod (sym : Symbol )(using Context ): Boolean =
363
+ val owner = sym.maybeOwner
364
+ owner.exists && (
365
+ owner.eq(defn.AnyClass ) ||
366
+ owner.isPrimitiveValueClass ||
367
+ owner.maybeOwner == defn.CompiletimePackageClass
368
+ )
369
+
342
370
object InstrumentCoverage :
343
371
val name : String = " instrumentCoverage"
344
372
val description : String = " instrument code for coverage checking"
373
+
374
+ /** Extractor object for trees produced by `insertInvokeCall`. */
375
+ object InstrumentedBlock :
376
+ private def isInvokedCall (app : Apply )(using Context ): Boolean =
377
+ app.span.isSynthetic && app.symbol == defn.InvokedMethodRef .symbol
378
+
379
+ def unapply (t : Tree )(using Context ): Option [(Apply , Tree )] =
380
+ t match
381
+ case Block ((app : Apply ) :: Nil , expr) if isInvokedCall(app) =>
382
+ Some ((app, expr))
383
+ case _ =>
384
+ None
0 commit comments