Skip to content

Commit b44e820

Browse files
enbntjenkins
authored and
jenkins
committed
util-app: make the App#flags field and Flags class final
Problem `App` is a trait that exposes non-final fields as `val`, which can result in access ordering issues when fields are overridden and then accessed by the base trait. While attempting to solve this problem, `Flags` overrides introduce other complications, as there are multiple ways to hook into flag parsing (via both `App#parseArgs` and `Flags#parseArgs`. Solution We move our `val` fields to `def` and expose the necessary properties that can be overridden to achieve the existing behavior, just more consistently. We make the `App#flags` def final and `Flags` class becomes final. The `App#name` val is similarly made a `def`. JIRA Issues: CSL-11231 Differential Revision: https://phabricator.twitter.biz/D723956
1 parent 72b9ba1 commit b44e820

File tree

3 files changed

+22
-4
lines changed

3 files changed

+22
-4
lines changed

CHANGELOG.rst

+12
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ Runtime Behavior Changes
2929

3030
* util: Bump version of Jackson to 2.11.4. ``PHAB_ID=D727879``
3131

32+
Breaking API Changes
33+
~~~~~~~~~~~~~~~~~~~~
34+
35+
* util-app: the `c.t.app.App#flags` field is now a `final def` instead of a `val` to address
36+
`override val` scenarios where the `c.t.app.App#flags` are accessed as part of construction,
37+
resulting in a NullPointerException due to access ordering issues.
38+
The `c.t.app.Flags` class is now made final. The `c.t.app.App#name` field has changed from
39+
a `val` and is now a `def`. A new `c.t.app.App#includeGlobalFlags` def has been exposed, which
40+
defaults to `true`. The `c.t.app#includeGlobalFlags` def can be overridden to `false`
41+
(ex: `override protected def includeGlobalFlags: Boolean = false`) in order to skip discovery
42+
of `GlobalFlag`s during flag parsing. ```PHAB_ID=D723956``
43+
3244
21.8.0 (No 21.7.0 Release)
3345
--------------------------
3446

util-app/src/main/scala/com/twitter/app/App.scala

+9-3
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,15 @@ import scala.util.control.NonFatal
4242
trait App extends ClosableOnce with CloseOnceAwaitably with Lifecycle {
4343

4444
/** The name of the application, based on the classname */
45-
val name: String = getClass.getName.stripSuffix("$")
45+
def name: String = getClass.getName.stripSuffix("$")
4646

47-
/** The [[com.twitter.app.Flags]] instance associated with this application */
4847
//failfastOnFlagsNotParsed is called in the ctor of App.scala here which is a bad idea
4948
//as things like this can happen https://stackoverflow.com/questions/18138397/calling-method-from-constructor
50-
val flag: Flags = new Flags(name, includeGlobal = true, failfastOnFlagsNotParsed)
49+
private[this] val _flag: Flags =
50+
new Flags(name, includeGlobal = includeGlobalFlags, failfastOnFlagsNotParsed)
51+
52+
/** The [[com.twitter.app.Flags]] instance associated with this application */
53+
final def flag: Flags = _flag
5154

5255
private var _args = Array[String]()
5356

@@ -57,6 +60,9 @@ trait App extends ClosableOnce with CloseOnceAwaitably with Lifecycle {
5760
/** Whether or not to accept undefined flags */
5861
protected def allowUndefinedFlags: Boolean = false
5962

63+
/** Whether or not to include [[GlobalFlag GlobalFlag's]] when [[Flags]] are parsed */
64+
protected def includeGlobalFlags: Boolean = true
65+
6066
/**
6167
* Users of this code should override this to `true` so that
6268
* you fail-fast instead of being surprised at runtime by code that

util-app/src/main/scala/com/twitter/app/Flags.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ object Flags {
8181
* be included during flag parsing. If false, only flags defined in the
8282
* application itself will be consulted.
8383
*/
84-
class Flags(argv0: String, includeGlobal: Boolean, failFastUntilParsed: Boolean) {
84+
final class Flags(argv0: String, includeGlobal: Boolean, failFastUntilParsed: Boolean) {
8585
import com.twitter.app.Flags._
8686

8787
def this(argv0: String, includeGlobal: Boolean) = this(argv0, includeGlobal, false)

0 commit comments

Comments
 (0)