Skip to content

Commit

Permalink
refactor: fix potential deadlocks in Java runtime (antlr4#3752)
Browse files Browse the repository at this point in the history
See antlr/antlr4#3752
Commit 875911c2b2e6e50317e75ff2b5e80f4014a75954
  • Loading branch information
lppedd committed Dec 5, 2023
1 parent 289ad45 commit 90ef7a5
Show file tree
Hide file tree
Showing 14 changed files with 84 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package org.antlr.v4.kotlinruntime
import org.antlr.v4.kotlinruntime.ast.Position
import org.antlr.v4.kotlinruntime.misc.Interval
import org.antlr.v4.kotlinruntime.tree.*
import kotlin.jvm.JvmStatic
import kotlin.reflect.KClass

//
Expand All @@ -34,6 +35,11 @@ import kotlin.reflect.KClass
// * satisfy the superclass interface.
// */
open class ParserRuleContext : RuleContext {
companion object {
@JvmStatic
val EMPTY: ParserRuleContext = ParserRuleContext()
}

// override fun setParent(parent: RuleContext) {
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
// }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,6 @@ import org.antlr.v4.kotlinruntime.tree.Trees
*
* @see ParserRuleContext
*/

val EMPTY_RULECTX = ParserRuleContext()


open class RuleContext : RuleNode {
// override fun setParent(parent: RuleContext) {
// TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
Expand Down Expand Up @@ -212,7 +208,7 @@ open class RuleContext : RuleNode {
}

// recog null unless ParserRuleContext, in which case we use subclass toString(...)
fun toString(recog: Recognizer<*, *>?, stop: RuleContext = EMPTY_RULECTX): String {
fun toString(recog: Recognizer<*, *>?, stop: RuleContext = ParserRuleContext.EMPTY): String {
val ruleNames = recog?.ruleNames
val ruleNamesList = if (ruleNames != null) listOf(*ruleNames) else null
return toString(ruleNamesList, stop)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ open class ATNConfig {
constructor(state: ATNState,
alt: Int,
context: PredictionContext?,
semanticContext: SemanticContext = SemanticContext.NONE) {
semanticContext: SemanticContext = SemanticContext.Empty.Instance) {
this.state = state
this.alt = alt
this.context = context
Expand Down Expand Up @@ -132,7 +132,7 @@ open class ATNConfig {

return (this.state.stateNumber == other.state.stateNumber
&& this.alt == other.alt
&& (this.context === other.context || (this.context != null && this.context == other.context))
&& this.context == other.context
&& this.semanticContext == other.semanticContext
&& this.isPrecedenceFilterSuppressed == other.isPrecedenceFilterSuppressed)
}
Expand Down Expand Up @@ -168,7 +168,7 @@ open class ATNConfig {
buf.append(context!!.toString())
buf.append("]")
}
if (semanticContext != null && semanticContext !== SemanticContext.NONE) {
if (semanticContext != null && semanticContext !== SemanticContext.Empty.Instance) {
buf.append(",")
buf.append(semanticContext)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ open class ATNConfigSet constructor(
get() {
val preds = ArrayList<SemanticContext>()
for (c in configs) {
if (c.semanticContext !== SemanticContext.NONE) {
if (c.semanticContext !== SemanticContext.Empty.Instance) {
preds.add(c.semanticContext!!)
}
}
Expand Down Expand Up @@ -178,7 +178,7 @@ open class ATNConfigSet constructor(
config: ATNConfig,
mergeCache: DoubleKeyMap<PredictionContext, PredictionContext, PredictionContext>?): Boolean {
if (isReadonly) throw IllegalStateException("This set is readonly")
if (config.semanticContext !== SemanticContext.NONE) {
if (config.semanticContext !== SemanticContext.Empty.Instance) {
hasSemanticContext = true
}
if (config.outerContextDepth > 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@

package org.antlr.v4.kotlinruntime.atn

import kotlin.jvm.JvmStatic

class EmptyPredictionContext : SingletonPredictionContext(null, PredictionContext.EMPTY_RETURN_STATE) {
companion object {
/**
* Represents `$` in local context prediction, which means wildcard.
* `*+x = *`.
*/
@JvmStatic
val Instance: EmptyPredictionContext = EmptyPredictionContext()
}

override val isEmpty: Boolean
get() = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class LL1Analyzer(val atn: ATN) {
look[alt] = IntervalSet()
val lookBusy = HashSet<ATNConfig>()
val seeThruPreds = false // fail to get lookahead upon pred
_LOOK(s!!.transition(alt).target!!, null, PredictionContext.EMPTY,
_LOOK(s!!.transition(alt).target!!, null, EmptyPredictionContext.Instance,
look[alt]!!, lookBusy, BitSet(), seeThruPreds, false)
// Wipe out lookahead for this alternative if we found nothing
// or we had a predicate when we !seeThruPreds
Expand Down Expand Up @@ -156,7 +156,7 @@ class LL1Analyzer(val atn: ATN) {
return
}

if (ctx !== PredictionContext.EMPTY) {
if (ctx !== EmptyPredictionContext.Instance) {
// run thru all possible stack tops in ctx
val removed = calledRuleStack.get(s.ruleIndex)
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ class LexerATNConfig : ATNConfig {

constructor(state: ATNState,
alt: Int,
context: PredictionContext) : super(state, alt, context, SemanticContext.NONE) {
context: PredictionContext) : super(state, alt, context, SemanticContext.Empty.Instance) {
this.passedThroughNonGreedyDecision = false
this.lexerActionExecutor = null
}

constructor(state: ATNState,
alt: Int,
context: PredictionContext,
lexerActionExecutor: LexerActionExecutor) : super(state, alt, context, SemanticContext.NONE) {
lexerActionExecutor: LexerActionExecutor) : super(state, alt, context, SemanticContext.Empty.Instance) {
this.lexerActionExecutor = lexerActionExecutor
this.passedThroughNonGreedyDecision = false
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ class LexerATNSimulator(protected val recog: Lexer?, atn: ATN,

protected fun computeStartState(input: CharStream,
p: ATNState): ATNConfigSet {
val initialContext = PredictionContext.EMPTY
val initialContext = EmptyPredictionContext.Instance
val configs = OrderedATNConfigSet()
for (i in 0 until p.numberOfTransitions) {
val target = p.transition(i).target
Expand Down Expand Up @@ -390,7 +390,7 @@ class LexerATNSimulator(protected val recog: Lexer?, atn: ATN,
configs.add(config)
return true
} else {
configs.add(LexerATNConfig(config, config.state, PredictionContext.EMPTY))
configs.add(LexerATNConfig(config, config.state, EmptyPredictionContext.Instance))
currentAltReachedAcceptState = true
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ open class ParserATNSimulator(
}

if (s0 == null) {
if (outerContext == null) outerContext = EMPTY_RULECTX
if (outerContext == null) outerContext = ParserRuleContext.EMPTY
if (debug || debug_list_atn_decisions) {
System.out.println("predictATN decision " + dfa.decision +
" exec LA(1)==" + getLookaheadName(input) +
Expand All @@ -359,7 +359,7 @@ open class ParserATNSimulator(

val fullCtx = false
var s0_closure = computeStartState(dfa.atnStartState,
EMPTY_RULECTX,
ParserRuleContext.EMPTY,
fullCtx)

if (dfa.isPrecedenceDfa) {
Expand Down Expand Up @@ -1176,8 +1176,8 @@ open class ParserATNSimulator(
var nPredAlts = 0
for (i in 1..nalts) {
if (altToPred!![i] == null) {
altToPred[i] = SemanticContext.NONE
} else if (altToPred[i] !== SemanticContext.NONE) {
altToPred[i] = SemanticContext.Empty.Instance
} else if (altToPred[i] !== SemanticContext.Empty.Instance) {
nPredAlts++
}
}
Expand Down Expand Up @@ -1206,7 +1206,7 @@ open class ParserATNSimulator(
if (ambigAlts != null && ambigAlts.get(i)) {
pairs.add(DFAState.PredPrediction(pred!!, i))
}
if (pred !== SemanticContext.NONE) containsPredicate = true
if (pred !== SemanticContext.Empty.Instance) containsPredicate = true
}

return if (!containsPredicate) {
Expand Down Expand Up @@ -1308,7 +1308,7 @@ open class ParserATNSimulator(
val succeeded = ATNConfigSet(configs.fullCtx)
val failed = ATNConfigSet(configs.fullCtx)
for (c in configs) {
if (c.semanticContext !== SemanticContext.NONE) {
if (c.semanticContext !== SemanticContext.Empty.Instance) {
val predicateEvaluationResult = evalSemanticContext(c.semanticContext, outerContext, c.alt, configs.fullCtx)
if (predicateEvaluationResult) {
succeeded.add(c)
Expand All @@ -1333,7 +1333,7 @@ open class ParserATNSimulator(
complete: Boolean): BitSet {
val predictions = BitSet()
for (pair in predPredictions) {
if (pair!!.pred === SemanticContext.NONE) {
if (pair!!.pred === SemanticContext.Empty.Instance) {
predictions.set(pair!!.alt)
if (!complete) {
break
Expand Down Expand Up @@ -1430,7 +1430,7 @@ open class ParserATNSimulator(
for (i in 0 until config.context!!.size()) {
if (config.context!!.getReturnState(i) == PredictionContext.EMPTY_RETURN_STATE) {
if (fullCtx) {
configs.add(ATNConfig(config, config.state, PredictionContext.EMPTY), mergeCache)
configs.add(ATNConfig(config, config.state, EmptyPredictionContext.Instance), mergeCache)
continue
} else {
// we have no context info, just chase follow links (if greedy)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package org.antlr.v4.kotlinruntime.atn

import com.strumenta.kotlinmultiplatform.IdentityHashMap
import com.strumenta.kotlinmultiplatform.assert
import org.antlr.v4.kotlinruntime.EMPTY_RULECTX
import org.antlr.v4.kotlinruntime.ParserRuleContext
import org.antlr.v4.kotlinruntime.Recognizer
import org.antlr.v4.kotlinruntime.RuleContext
import org.antlr.v4.kotlinruntime.misc.DoubleKeyMap
Expand Down Expand Up @@ -38,9 +38,9 @@ abstract class PredictionContext protected constructor(
*/
val cachedHashCode: Int
) {
/** This means only the [.EMPTY] (wildcard? not sure) context is in set. */
/** This means only the [EmptyPredictionContext.Instance] (wildcard? not sure) context is in set. */
open val isEmpty: Boolean
get() = this === EMPTY
get() = this === EmptyPredictionContext.Instance

abstract fun size(): Int

Expand All @@ -65,7 +65,7 @@ abstract class PredictionContext protected constructor(
}

fun toStrings(recognizer: Recognizer<*, *>, currentState: Int): Array<String> {
return toStrings(recognizer, EMPTY, currentState)
return toStrings(recognizer, EmptyPredictionContext.Instance, currentState)
}

// FROM SAM
Expand Down Expand Up @@ -134,12 +134,6 @@ abstract class PredictionContext protected constructor(
}

companion object {
/**
* Represents `$` in local context prediction, which means wildcard.
* `*+x = *`.
*/
val EMPTY = EmptyPredictionContext()

/**
* Represents `$` in an array in full context mode, when `$`
* doesn't mean wildcard: `$ + x = [$,x]`. Here,
Expand All @@ -150,15 +144,15 @@ abstract class PredictionContext protected constructor(
private const val INITIAL_HASH = 1

/** Convert a [RuleContext] tree to a [PredictionContext] graph.
* Return [.EMPTY] if `outerContext` is empty or null.
* Return [EmptyPredictionContext.Instance] if `outerContext` is empty or null.
*/
fun fromRuleContext(atn: ATN, outerContext: RuleContext?): PredictionContext {
val outerContext1 = outerContext ?: EMPTY_RULECTX
val outerContext1 = outerContext ?: ParserRuleContext.EMPTY

// if we are in RuleContext of start rule, s, then PredictionContext
// is EMPTY. Nobody called us. (if we are empty, return empty)
if (outerContext1.readParent() == null || outerContext1 === EMPTY_RULECTX) {
return EMPTY
if (outerContext1.readParent() == null || outerContext1 === ParserRuleContext.EMPTY) {
return EmptyPredictionContext.Instance
}

// If we have a parent, convert it to a PredictionContext graph
Expand Down Expand Up @@ -337,8 +331,8 @@ abstract class PredictionContext protected constructor(

/**
* Handle case where at least one of `a` or `b` is
* [.EMPTY]. In the following diagrams, the symbol `$` is used
* to represent [.EMPTY].
* [EmptyPredictionContext.Instance]. In the following diagrams, the symbol `$` is used
* to represent [EmptyPredictionContext.Instance].
*
* <h2>Local-Context Merges</h2>
*
Expand All @@ -347,11 +341,11 @@ abstract class PredictionContext protected constructor(
* is true.
*
*
* [.EMPTY] is superset of any graph; return [.EMPTY].<br></br>
* [EmptyPredictionContext.Instance] is superset of any graph; return [EmptyPredictionContext.Instance].<br></br>
* <embed src="images/LocalMerge_EmptyRoot.svg" type="image/svg+xml"></embed>
*
*
* [.EMPTY] and anything is `#EMPTY`, so merged parent is
* [EmptyPredictionContext.Instance] and anything is `#EMPTY`, so merged parent is
* `#EMPTY`; return left graph.<br></br>
* <embed src="images/LocalMerge_EmptyParent.svg" type="image/svg+xml"></embed>
*
Expand All @@ -369,7 +363,7 @@ abstract class PredictionContext protected constructor(
* <embed src="images/FullMerge_EmptyRoots.svg" type="image/svg+xml"></embed>
*
*
* Must keep all contexts; [.EMPTY] in array is a special value (and
* Must keep all contexts; [EmptyPredictionContext.Instance] in array is a special value (and
* null parent).<br></br>
* <embed src="images/FullMerge_EmptyRoot.svg" type="image/svg+xml"></embed>
*
Expand All @@ -387,17 +381,17 @@ abstract class PredictionContext protected constructor(
rootIsWildcard: Boolean
): PredictionContext? {
if (rootIsWildcard) {
if (a === EMPTY) return EMPTY // * + b = *
if (b === EMPTY) return EMPTY // a + * = *
if (a === EmptyPredictionContext.Instance) return EmptyPredictionContext.Instance // * + b = *
if (b === EmptyPredictionContext.Instance) return EmptyPredictionContext.Instance // a + * = *
} else {
if (a === EMPTY && b === EMPTY) return EMPTY // $ + $ = $
if (a === EmptyPredictionContext.Instance && b === EmptyPredictionContext.Instance) return EmptyPredictionContext.Instance // $ + $ = $

if (a === EMPTY) { // $ + x = [x,$]
if (a === EmptyPredictionContext.Instance) { // $ + x = [x,$]
val payloads = intArrayOf(b.returnState, EMPTY_RETURN_STATE)
val parents = arrayOf(b.parent, null)
return ArrayPredictionContext(parents, payloads)
}
if (b === EMPTY) { // x + $ = [x,$] ($ is always last if present)
if (b === EmptyPredictionContext.Instance) { // x + $ = [x,$] ($ is always last if present)
val payloads = intArrayOf(a.returnState, EMPTY_RETURN_STATE)
val parents = arrayOf(a.parent, null)
return ArrayPredictionContext(parents, payloads)
Expand Down Expand Up @@ -599,7 +593,7 @@ abstract class PredictionContext protected constructor(

val updated: PredictionContext
if (parents.size == 0) {
updated = EMPTY
updated = EmptyPredictionContext.Instance
} else if (parents.size == 1) {
updated = SingletonPredictionContext.create(parents[0], context.getReturnState(0))
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class PredictionContextCache {
* Protect shared cache from unsafe thread access.
*/
fun add(ctx: PredictionContext): PredictionContext {
if (ctx === PredictionContext.EMPTY) return PredictionContext.EMPTY
if (ctx === EmptyPredictionContext.Instance) return EmptyPredictionContext.Instance
val existing = cache[ctx]
if (existing != null) {
// System.out.println(name+" reuses "+existing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ enum class PredictionMode {
val dup = ATNConfigSet()
for (c in configs) {
var mutableC = c
mutableC = ATNConfig(mutableC, SemanticContext.NONE)
mutableC = ATNConfig(mutableC, SemanticContext.Empty.Instance)
dup.add(mutableC)
}
configs = dup
Expand Down
Loading

0 comments on commit 90ef7a5

Please sign in to comment.