@@ -2,23 +2,21 @@ package dotty.tools.dotc
22package transform
33
44import java .io .File
5- import java .util .concurrent .atomic .AtomicInteger
65
76import ast .tpd .*
87import collection .mutable
98import core .Flags .*
109import core .Contexts .{Context , ctx , inContext }
1110import core .DenotTransformers .IdentityDenotTransformer
1211import core .Symbols .{defn , Symbol }
13- import core .Decorators .{toTermName , i }
1412import core .Constants .Constant
1513import core .NameOps .isContextFunction
1614import core .Types .*
15+ import coverage .*
1716import typer .LiftCoverage
18- import util .{ SourcePosition , Property }
17+ import util .SourcePosition
1918import util .Spans .Span
20- import coverage .*
21- import localopt .StringInterpolatorOpt .isCompilerIntrinsic
19+ import localopt .StringInterpolatorOpt
2220
2321/** Implements code coverage by inserting calls to scala.runtime.coverage.Invoker
2422 * ("instruments" the source code).
@@ -44,7 +42,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
4442 val outputPath = ctx.settings.coverageOutputDir.value
4543
4644 // Ensure the dir exists
47- val dataDir = new File (outputPath)
45+ val dataDir = File (outputPath)
4846 val newlyCreated = dataDir.mkdirs()
4947
5048 if ! newlyCreated then
@@ -66,7 +64,16 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
6664 tree match
6765 // simple cases
6866 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
7077
7178 // branches
7279 case tree : If =>
@@ -82,20 +89,6 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
8289 finalizer = instrument(transform(tree.finalizer), branch = true )
8390 )
8491
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-
9992 // f(args)
10093 case tree : Apply =>
10194 if canInstrumentApply(tree) then
@@ -106,24 +99,36 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
10699 else
107100 transformApply(tree)
108101
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)
112122
113123 // a.b
114124 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 )
120130 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
127132
128133 case tree : CaseDef => instrumentCaseDef(tree)
129134 case tree : ValDef =>
@@ -142,7 +147,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
142147 val rhs = transform(tree.rhs)
143148 val finalRhs =
144149 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.
146153 instrumentBody(tree, rhs)
147154 else
148155 rhs
@@ -162,7 +169,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
162169 }
163170
164171 /** 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) .
166173 */
167174 private def instrumentLifted (tree : Apply )(using Context ) =
168175 // lifting
@@ -178,10 +185,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
178185 )
179186
180187 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))
185189
186190 private inline def instrumentCases (cases : List [CaseDef ])(using Context ): List [CaseDef ] =
187191 cases.map(instrumentCaseDef)
@@ -201,7 +205,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
201205 private def recordStatement (tree : Tree , pos : SourcePosition , branch : Boolean )(using ctx : Context ): Int =
202206 val id = statementId
203207 statementId += 1
204- val statement = new Statement (
208+ val statement = Statement (
205209 source = ctx.source.file.name,
206210 location = Location (tree),
207211 id = id,
@@ -255,12 +259,12 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
255259 val statementId = recordStatement(parent, pos, false )
256260 insertInvokeCall(body, pos, statementId)
257261
258- /** Returns the tree, prepended by a call to Invoker.invoker */
262+ /** Returns the tree, prepended by a call to Invoker.invoked */
259263 private def insertInvokeCall (tree : Tree , pos : SourcePosition , statementId : Int )(using Context ): Tree =
260264 val callSpan = syntheticSpan(pos)
261265 Block (invokeCall(statementId, callSpan) :: Nil , tree).withSpan(callSpan.union(tree.span))
262266
263- /** Generates Invoked .invoked(id, DIR) */
267+ /** Generates Invoker .invoked(id, DIR) */
264268 private def invokeCall (id : Int , span : Span )(using Context ): Tree =
265269 val outputPath = ctx.settings.coverageOutputDir.value
266270 ref(defn.InvokedMethodRef ).withSpan(span)
@@ -292,7 +296,7 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
292296 * they shouldn't be lifted.
293297 */
294298 val sym = fun.symbol
295- sym.exists && (isShortCircuitedOp(sym) || isCompilerIntrinsic(sym))
299+ sym.exists && (isShortCircuitedOp(sym) || StringInterpolatorOpt . isCompilerIntrinsic(sym))
296300 end
297301
298302 val fun = tree.fun
@@ -312,7 +316,9 @@ class InstrumentCoverage extends MacroTransform with IdentityDenotTransformer:
312316
313317 /** Check if an Apply can be instrumented. Prevents this phase from generating incorrect code. */
314318 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) &&
316322 (tree.typeOpt match
317323 case AppliedType (tycon : NamedType , _) =>
318324 /* 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:
339345 true
340346 )
341347
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+
342370object InstrumentCoverage :
343371 val name : String = " instrumentCoverage"
344372 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