[LL FIR] fix user code leak from exceptions in :analysis:analysis-api-fir

Also, add more info to some exceptions
This commit is contained in:
Ilya Kirillov
2023-07-24 17:51:15 +02:00
committed by Space Team
parent e3d2bccb33
commit 7606aab0dd
12 changed files with 80 additions and 31 deletions
@@ -623,7 +623,9 @@ private class BuilderCache<From, To : KtSymbol> {
inline fun <reified S : To> cache(key: From, calculation: () -> S): S { inline fun <reified S : To> cache(key: From, calculation: () -> S): S {
val value = cache.getOrPut(key, calculation) val value = cache.getOrPut(key, calculation)
return value as? S return value as? S
?: error("Cannot cast ${value::class} to ${S::class}\n${value}") ?: errorWithAttachment("Cannot cast ${value::class} to ${S::class}") {
withEntry("value", value.toString())
}
} }
} }
@@ -71,6 +71,7 @@ import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS import org.jetbrains.kotlin.util.OperatorNameConventions.EQUALS
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
internal class KtFirCallResolver( internal class KtFirCallResolver(
override val analysisSession: KtFirAnalysisSession, override val analysisSession: KtFirAnalysisSession,
@@ -403,8 +404,13 @@ internal class KtFirCallResolver(
val explicitReceiverPsi = when (psi) { val explicitReceiverPsi = when (psi) {
is KtQualifiedExpression -> (psi.selectorExpression as KtCallExpression).calleeExpression is KtQualifiedExpression -> (psi.selectorExpression as KtCallExpression).calleeExpression
is KtCallExpression -> psi.calleeExpression is KtCallExpression -> psi.calleeExpression
else -> error("unexpected PSI $psi for FirImplicitInvokeCall") else -> errorWithAttachment("unexpected PSI ${psi::class} for FirImplicitInvokeCall") {
} ?: error("missing calleeExpression in PSI $psi for FirImplicitInvokeCall") withPsiEntry("psi", psi, analysisSession::getModule)
}
}
?: errorWithAttachment("missing calleeExpression in PSI ${psi::class} for FirImplicitInvokeCall") {
withPsiEntry("psi", psi, analysisSession::getModule)
}
// Specially handle @ExtensionFunctionType // Specially handle @ExtensionFunctionType
if (dispatchReceiver.typeRef.coneTypeSafe<ConeKotlinType>()?.isExtensionFunctionType == true) { if (dispatchReceiver.typeRef.coneTypeSafe<ConeKotlinType>()?.isExtensionFunctionType == true) {
@@ -31,6 +31,8 @@ import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression
import org.jetbrains.kotlin.psi.KtSimpleNameExpression import org.jetbrains.kotlin.psi.KtSimpleNameExpression
import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForReceiver
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
internal class KtFirCompletionCandidateChecker( internal class KtFirCompletionCandidateChecker(
override val analysisSession: KtFirAnalysisSession, override val analysisSession: KtFirAnalysisSession,
@@ -90,7 +92,9 @@ internal class KtFirCompletionCandidateChecker(
): Sequence<ImplicitReceiverValue<*>?> { ): Sequence<ImplicitReceiverValue<*>?> {
val towerDataContext = analysisSession.firResolveSession.getTowerContextProvider(originalFile) val towerDataContext = analysisSession.firResolveSession.getTowerContextProvider(originalFile)
.getClosestAvailableParentContext(fakeNameExpression) .getClosestAvailableParentContext(fakeNameExpression)
?: error("Cannot find enclosing declaration for ${fakeNameExpression.getElementTextWithContext()}") ?: errorWithAttachment("Cannot find enclosing declaration for ${fakeNameExpression::class}") {
withPsiEntry("fakeNameExpression", fakeNameExpression)
}
return sequence { return sequence {
yield(null) // otherwise explicit receiver won't be checked when there are no implicit receivers in completion position yield(null) // otherwise explicit receiver won't be checked when there are no implicit receivers in completion position
@@ -23,6 +23,8 @@ import org.jetbrains.kotlin.fir.resolve.transformers.FirWhenExhaustivenessTransf
import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations import org.jetbrains.kotlin.psi.psiUtil.unwrapParenthesesLabelsAndAnnotations
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
internal class KtFirExpressionInfoProvider( internal class KtFirExpressionInfoProvider(
override val analysisSession: KtFirAnalysisSession, override val analysisSession: KtFirAnalysisSession,
@@ -172,8 +174,9 @@ internal class KtFirExpressionInfoProvider(
false false
!is KtExpression -> !is KtExpression ->
error("Unhandled Non-KtExpression parent of KtExpression: ${parent::class}") errorWithAttachment("Unhandled Non-KtExpression parent of KtExpression: ${parent::class}") {
withPsiEntry("parent", parent)
}
/** /**
* EXPRESSIONS * EXPRESSIONS
*/ */
@@ -351,7 +354,9 @@ internal class KtFirExpressionInfoProvider(
false false
else -> else ->
error("Unhandled KtElement subtype: ${parent::class}") errorWithAttachment("Unhandled KtElement subtype: ${parent::class}") {
withPsiEntry("parent", parent)
}
} }
} }
} }
@@ -42,6 +42,8 @@ import org.jetbrains.kotlin.psi.psiUtil.unwrapNullability
import org.jetbrains.kotlin.resolve.ImportPath import org.jetbrains.kotlin.resolve.ImportPath
import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny import org.jetbrains.kotlin.resolve.calls.util.getCalleeExpressionIfAny
import org.jetbrains.kotlin.util.OperatorNameConventions import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
internal class KtFirImportOptimizer( internal class KtFirImportOptimizer(
override val token: KtLifetimeToken, override val token: KtLifetimeToken,
@@ -444,7 +446,9 @@ private sealed interface TypeQualifier {
} }
qualifier.getCalleeExpressionIfAny() as? KtNameReferenceExpression qualifier.getCalleeExpressionIfAny() as? KtNameReferenceExpression
?: error("Cannot get referenced name from '${qualifier.text}'") ?: errorWithAttachment("Cannot get referenced name from '${qualifier::class}'") {
withPsiEntry("qualifier", qualifier)
}
} }
override val referencedByName: Name override val referencedByName: Name
@@ -469,7 +473,9 @@ private sealed interface TypeQualifier {
override val referencedByName: Name override val referencedByName: Name
get() = qualifier.referenceExpression?.getReferencedNameAsName() get() = qualifier.referenceExpression?.getReferencedNameAsName()
?: error("Cannot get referenced name from '${qualifier.text}'") ?: errorWithAttachment("Cannot get referenced name from '${qualifier::class}'") {
withPsiEntry("qualifier", qualifier)
}
override val isQualified: Boolean override val isQualified: Boolean
get() = qualifier.qualifier != null get() = qualifier.qualifier != null
@@ -66,6 +66,7 @@ import org.jetbrains.kotlin.psi.psiUtil.*
import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability import org.jetbrains.kotlin.resolve.calls.tower.CandidateApplicability
import org.jetbrains.kotlin.utils.addIfNotNull import org.jetbrains.kotlin.utils.addIfNotNull
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
internal class KtFirReferenceShortener( internal class KtFirReferenceShortener(
override val analysisSession: KtFirAnalysisSession, override val analysisSession: KtFirAnalysisSession,
@@ -520,7 +521,9 @@ private class ElementsToShortenCollector(
is FirNestedClassifierScope -> klass.classId is FirNestedClassifierScope -> klass.classId
is FirNestedClassifierScopeWithSubstitution -> originalScope.correspondingClassIdIfExists() is FirNestedClassifierScopeWithSubstitution -> originalScope.correspondingClassIdIfExists()
is FirClassUseSiteMemberScope -> classId is FirClassUseSiteMemberScope -> classId
else -> error("FirScope `$this` is expected to be one of FirNestedClassifierScope and FirClassUseSiteMemberScope to get ClassId") else -> errorWithAttachment("FirScope ${this::class}` is expected to be one of FirNestedClassifierScope and FirClassUseSiteMemberScope to get ClassId") {
withEntry("firScope", this@correspondingClassIdIfExists) { it.toString() }
}
} }
private fun ClassId.idWithoutCompanion() = if (shortClassName == SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) outerClassId else this private fun ClassId.idWithoutCompanion() = if (shortClassName == SpecialNames.DEFAULT_NAME_FOR_COMPANION_OBJECT) outerClassId else this
@@ -1095,7 +1098,9 @@ private class ElementsToShortenCollector(
val distinctCandidates = candidates.distinctBy { it.callableId } val distinctCandidates = candidates.distinctBy { it.callableId }
return distinctCandidates.singleOrNull() return distinctCandidates.singleOrNull()
?: error("Expected all candidates to have same callableId, but got: ${distinctCandidates.map { it.callableId }}") ?: errorWithAttachment("Expected all candidates to have same callableId but some of them but was different") {
withEntry("callableIds", distinctCandidates.map { it.callableId.asSingleFqName() }.joinToString())
}
} }
private fun findFakePackageToShorten(wholeQualifiedExpression: KtDotQualifiedExpression): ShortenQualifier? { private fun findFakePackageToShorten(wholeQualifiedExpression: KtDotQualifiedExpression): ShortenQualifier? {
@@ -51,6 +51,8 @@ import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.utils.addToStdlib.applyIf import org.jetbrains.kotlin.utils.addToStdlib.applyIf
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
internal class KtFirScopeProvider( internal class KtFirScopeProvider(
override val analysisSession: KtFirAnalysisSession, override val analysisSession: KtFirAnalysisSession,
@@ -194,7 +196,9 @@ internal class KtFirScopeProvider(
): KtScopeContext { ): KtScopeContext {
val towerDataContext = val towerDataContext =
analysisSession.firResolveSession.getTowerContextProvider(originalFile).getClosestAvailableParentContext(positionInFakeFile) analysisSession.firResolveSession.getTowerContextProvider(originalFile).getClosestAvailableParentContext(positionInFakeFile)
?: error("Cannot find enclosing declaration for ${positionInFakeFile.getElementTextWithContext()}") ?: errorWithAttachment("Cannot find enclosing declaration for ${positionInFakeFile::class}") {
withPsiEntry("positionInFakeFile", positionInFakeFile)
}
val towerDataElementsIndexed = towerDataContext.towerDataElements.asReversed().withIndex() val towerDataElementsIndexed = towerDataContext.towerDataElements.asReversed().withIndex()
val implicitReceivers = towerDataElementsIndexed.flatMap { (index, towerDataElement) -> val implicitReceivers = towerDataElementsIndexed.flatMap { (index, towerDataElement) ->
@@ -17,6 +17,8 @@ import org.jetbrains.kotlin.fir.contracts.description.*
import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic import org.jetbrains.kotlin.fir.diagnostics.ConeDiagnostic
import org.jetbrains.kotlin.contracts.description.LogicOperationKind import org.jetbrains.kotlin.contracts.description.LogicOperationKind
import org.jetbrains.kotlin.fir.types.ConeKotlinType import org.jetbrains.kotlin.fir.types.ConeKotlinType
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
internal fun KtEffectDeclaration<ConeKotlinType, ConeDiagnostic>.coneEffectDeclarationToAnalysisApi( internal fun KtEffectDeclaration<ConeKotlinType, ConeDiagnostic>.coneEffectDeclarationToAnalysisApi(
builder: KtSymbolByFirBuilder, builder: KtSymbolByFirBuilder,
@@ -51,12 +53,16 @@ private class ConeContractDescriptionElementToAnalysisApi(
when (value) { when (value) {
ConeContractConstantValues.TRUE -> KtContractConstantType.TRUE ConeContractConstantValues.TRUE -> KtContractConstantType.TRUE
ConeContractConstantValues.FALSE -> KtContractConstantType.FALSE ConeContractConstantValues.FALSE -> KtContractConstantType.FALSE
else -> error("Can't convert $value to the Analysis API") else -> errorWithAttachment("Can't convert ${value::class} to the Analysis API") {
withEntry("value", value) { value.toString() }
}
}, },
builder.token builder.token
) )
) )
else -> error("Can't convert $returnsEffect to the Analysis API") else -> errorWithAttachment("Can't convert ${returnsEffect::class} to the Analysis API") {
withEntry("value", value) { value.toString() }
}
} }
override fun visitCallsEffectDeclaration(callsEffect: KtCallsEffectDeclaration<ConeKotlinType, ConeDiagnostic>, data: Unit): KtContractCallsInPlaceContractEffectDeclaration = override fun visitCallsEffectDeclaration(callsEffect: KtCallsEffectDeclaration<ConeKotlinType, ConeDiagnostic>, data: Unit): KtContractCallsInPlaceContractEffectDeclaration =
@@ -116,7 +122,9 @@ private class ConeContractDescriptionElementToAnalysisApi(
constructor: (KtParameterSymbol) -> T constructor: (KtParameterSymbol) -> T
): T = constructor( ): T = constructor(
if (valueParameterReference.parameterIndex == -1) firFunctionSymbol.receiverParameter if (valueParameterReference.parameterIndex == -1) firFunctionSymbol.receiverParameter
?: error("$firFunctionSymbol should contain a receiver") ?: errorWithAttachment("${firFunctionSymbol::class} should contain a receiver") {
withFirEntry("fir", firFunctionSymbol.firSymbol.fir)
}
else firFunctionSymbol.valueParameters[valueParameterReference.parameterIndex] else firFunctionSymbol.valueParameters[valueParameterReference.parameterIndex]
) )
@@ -22,6 +22,9 @@ import org.jetbrains.kotlin.analysis.api.symbols.pointers.KtSymbolPointer
import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.requireWithAttachment
internal class KtFirReceiverParameterSymbol( internal class KtFirReceiverParameterSymbol(
val firSymbol: FirCallableSymbol<*>, val firSymbol: FirCallableSymbol<*>,
@@ -31,11 +34,16 @@ internal class KtFirReceiverParameterSymbol(
override val psi: PsiElement? = withValidityAssertion{ firSymbol.fir.receiverParameter?.typeRef?.psi } override val psi: PsiElement? = withValidityAssertion{ firSymbol.fir.receiverParameter?.typeRef?.psi }
init { init {
require(firSymbol.fir.receiverParameter != null) { "$firSymbol doesn't have an extension receiver." } requireWithAttachment(firSymbol.fir.receiverParameter != null, { "${firSymbol::class} doesn't have an extension receiver." }) {
withFirEntry("callable", firSymbol.fir)
}
} }
override val type: KtType by cached { override val type: KtType by cached {
firSymbol.receiverType(analysisSession.firSymbolBuilder) ?: error("$firSymbol doesn't have an extension receiver.") firSymbol.receiverType(analysisSession.firSymbolBuilder)
?: errorWithAttachment("${firSymbol::class} doesn't have an extension receiver") {
withFirEntry("callable", firSymbol.fir)
}
} }
override val owningCallableSymbol: KtCallableSymbol by cached { analysisSession.firSymbolBuilder.callableBuilder.buildCallableSymbol(firSymbol) } override val owningCallableSymbol: KtCallableSymbol by cached { analysisSession.firSymbolBuilder.callableBuilder.buildCallableSymbol(firSymbol) }
@@ -25,7 +25,9 @@ import org.jetbrains.kotlin.fir.declarations.FirFunction
import org.jetbrains.kotlin.fir.renderWithType import org.jetbrains.kotlin.fir.renderWithType
import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirValueParameterSymbol
import org.jetbrains.kotlin.fir.types.arrayElementType import org.jetbrains.kotlin.fir.types.arrayElementType
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
internal class KtFirValueParameterSymbol( internal class KtFirValueParameterSymbol(
override val firSymbol: FirValueParameterSymbol, override val firSymbol: FirValueParameterSymbol,
@@ -51,7 +53,9 @@ internal class KtFirValueParameterSymbol(
return@cached if (firSymbol.isVararg) { return@cached if (firSymbol.isVararg) {
// There SHOULD always be an array element type (even if it is an error type, e.g., unresolved). // There SHOULD always be an array element type (even if it is an error type, e.g., unresolved).
val arrayElementType = returnType.arrayElementType() val arrayElementType = returnType.arrayElementType()
?: error("No array element type for vararg value parameter: ${firSymbol.fir.renderWithType()}") ?: errorWithAttachment("No array element type for vararg value parameter") {
withFirEntry("fir", firSymbol.fir)
}
builder.typeBuilder.buildKtType(arrayElementType) builder.typeBuilder.buildKtType(arrayElementType)
} else { } else {
builder.typeBuilder.buildKtType(returnType) builder.typeBuilder.buildKtType(returnType)
@@ -16,31 +16,24 @@ import org.jetbrains.kotlin.analysis.api.impl.base.KtContextReceiverImpl
import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol import org.jetbrains.kotlin.analysis.api.symbols.KtValueParameterSymbol
import org.jetbrains.kotlin.analysis.api.types.KtType import org.jetbrains.kotlin.analysis.api.types.KtType
import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession import org.jetbrains.kotlin.analysis.low.level.api.fir.api.LLFirResolveSession
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.declarations.*
import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression import org.jetbrains.kotlin.fir.expressions.FirPropertyAccessExpression
import org.jetbrains.kotlin.fir.psi
import org.jetbrains.kotlin.fir.references.impl.FirPropertyFromParameterResolvedNamedReference import org.jetbrains.kotlin.fir.references.impl.FirPropertyFromParameterResolvedNamedReference
import org.jetbrains.kotlin.fir.renderer.FirRenderer
import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol import org.jetbrains.kotlin.fir.symbols.FirBasedSymbol
import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase import org.jetbrains.kotlin.fir.symbols.lazyResolveToPhase
import org.jetbrains.kotlin.fir.symbols.impl.* import org.jetbrains.kotlin.fir.symbols.impl.*
import org.jetbrains.kotlin.fir.types.toRegularClassSymbol import org.jetbrains.kotlin.fir.types.toRegularClassSymbol
import org.jetbrains.kotlin.fir.utils.exceptions.withFirEntry
import org.jetbrains.kotlin.name.CallableId import org.jetbrains.kotlin.name.CallableId
import org.jetbrains.kotlin.name.ClassId import org.jetbrains.kotlin.name.ClassId
import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
internal fun FirCallableSymbol<*>.invalidModalityError(): Nothing { internal fun FirCallableSymbol<*>.invalidModalityError(): Nothing {
val rendered = FirRenderer.withResolvePhase().renderElementWithTypeAsString(fir) errorWithAttachment("Symbol modality should not be null, looks like the FIR symbol was not properly resolved") {
error( withFirEntry("fir", this@invalidModalityError.fir)
"""|Symbol modality should not be null, looks like the FIR symbol was not properly resolved }
|
|$rendered
|
|${(fir.psi as? KtDeclaration)?.getElementTextWithContext()}""".trimMargin()
)
} }
internal fun FirFunctionSymbol<*>.createKtValueParameters(builder: KtSymbolByFirBuilder): List<KtValueParameterSymbol> { internal fun FirFunctionSymbol<*>.createKtValueParameters(builder: KtSymbolByFirBuilder): List<KtValueParameterSymbol> {
@@ -21,6 +21,8 @@ import org.jetbrains.kotlin.fir.resolve.toSymbol
import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol import org.jetbrains.kotlin.fir.symbols.impl.FirRegularClassSymbol
import org.jetbrains.kotlin.fir.symbols.impl.LookupTagInternals import org.jetbrains.kotlin.fir.symbols.impl.LookupTagInternals
import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl import org.jetbrains.kotlin.fir.types.impl.ConeClassLikeTypeImpl
import org.jetbrains.kotlin.utils.exceptions.errorWithAttachment
import org.jetbrains.kotlin.utils.exceptions.withPsiEntry
internal object UsualClassTypeQualifierBuilder { internal object UsualClassTypeQualifierBuilder {
fun buildQualifiers( fun buildQualifiers(
@@ -104,7 +106,9 @@ internal object UsualClassTypeQualifierBuilder {
is FirAnonymousObject -> listOf(declaration) is FirAnonymousObject -> listOf(declaration)
is FirRegularClass -> declaration.collectForLocal() is FirRegularClass -> declaration.collectForLocal()
is FirTypeAlias -> listOf(declaration) // TODO: handle type aliases is FirTypeAlias -> listOf(declaration) // TODO: handle type aliases
else -> error("Invalid declaration ${declaration.renderWithType()}") else -> errorWithAttachment("Invalid declaration ${declaration::class}") {
withFirEntry("declaration", declaration)
}
} }
} }
} }