Merge pull request #7507 from michaelnebel/csharp-libdataflow-cleanup

C#: Refactor and cleanup LibraryTypeDataFlow
This commit is contained in:
Michael Nebel
2022-01-07 13:16:08 +01:00
committed by GitHub
6 changed files with 93 additions and 552 deletions

View File

@@ -10,7 +10,6 @@ class ArgumentPosition = DataFlowDispatch::ArgumentPosition;
// import all instances below
private module Summaries {
private import semmle.code.csharp.dataflow.LibraryTypeDataFlow
private import semmle.code.csharp.frameworks.EntityFramework
}
@@ -111,6 +110,29 @@ module SummaryComponentStack {
class SummarizedCallable = Impl::Public::SummarizedCallable;
private predicate recordConstructorFlow(Constructor c, int i, Property p) {
c = any(Record r).getAMember() and
exists(string name |
c.getParameter(i).getName() = name and
c.getDeclaringType().getAMember(name) = p
)
}
private class RecordConstructorFlow extends SummarizedCallable {
RecordConstructorFlow() { recordConstructorFlow(this, _, _) }
override predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
exists(int i, Property p |
recordConstructorFlow(this, i, p) and
input = SummaryComponentStack::argument(i) and
output = SummaryComponentStack::propertyOf(p, SummaryComponentStack::return()) and
preservesValue = true
)
}
}
private class SummarizedCallableDefaultClearsContent extends Impl::Public::SummarizedCallable {
SummarizedCallableDefaultClearsContent() {
this instanceof Impl::Public::SummarizedCallable or none()
@@ -129,3 +151,17 @@ private class SummarizedCallableDefaultClearsContent extends Impl::Public::Summa
}
class RequiredSummaryComponentStack = Impl::Public::RequiredSummaryComponentStack;
private class RecordConstructorFlowRequiredSummaryComponentStack extends RequiredSummaryComponentStack {
private SummaryComponent head;
RecordConstructorFlowRequiredSummaryComponentStack() {
exists(Property p |
recordConstructorFlow(_, _, p) and
head = SummaryComponent::property(p) and
this = SummaryComponentStack::return()
)
}
override predicate required(SummaryComponent c) { c = head }
}

View File

@@ -1,551 +0,0 @@
/**
* Provides classes and predicates for tracking data flow through library types.
*/
import csharp
private import semmle.code.csharp.frameworks.system.Collections
private import semmle.code.csharp.frameworks.system.linq.Expressions
private import semmle.code.csharp.frameworks.system.Text
private import semmle.code.csharp.frameworks.system.runtime.CompilerServices
private import semmle.code.csharp.frameworks.system.threading.Tasks
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.DataFlowPublic
private import semmle.code.csharp.dataflow.internal.DelegateDataFlow
// import `LibraryTypeDataFlow` definitions from other files to avoid potential reevaluation
private import semmle.code.csharp.frameworks.EntityFramework
private import FlowSummary
private newtype TAccessPath =
TNilAccessPath() or
TConsAccessPath(Content head, AccessPath tail) {
tail = TNilAccessPath()
or
exists(LibraryTypeDataFlow ltdf |
ltdf.requiresAccessPath(head, tail) and
tail.length() < accessPathLimit()
)
or
tail = AccessPath::singleton(_) and
head instanceof ElementContent
or
tail = AccessPath::element()
}
/** An access path. */
class AccessPath extends TAccessPath {
/** Gets the head of this access path, if any. */
Content getHead() { this = TConsAccessPath(result, _) }
/** Gets the tail of this access path, if any. */
AccessPath getTail() { this = TConsAccessPath(_, result) }
/** Gets the length of this access path. */
int length() {
this = TNilAccessPath() and result = 0
or
result = 1 + this.getTail().length()
}
/** Gets the access path obtained by dropping the first `i` elements, if any. */
AccessPath drop(int i) {
i = 0 and result = this
or
result = this.getTail().drop(i - 1)
}
/** Holds if this access path contains content `c`. */
predicate contains(Content c) { c = this.drop(_).getHead() }
/** Gets a textual representation of this access path. */
string toString() {
exists(Content head, AccessPath tail |
head = this.getHead() and
tail = this.getTail() and
if tail.length() = 0 then result = head.toString() else result = head + ", " + tail
)
or
this = TNilAccessPath() and
result = "<empty>"
}
}
/** Provides predicates for constructing access paths. */
module AccessPath {
/** Gets the empty access path. */
AccessPath empty() { result = TNilAccessPath() }
/** Gets a singleton access path containing `c`. */
AccessPath singleton(Content c) { result = TConsAccessPath(c, TNilAccessPath()) }
/** Gets the access path obtained by concatenating `head` onto `tail`. */
AccessPath cons(Content head, AccessPath tail) { result = TConsAccessPath(head, tail) }
/** Gets the singleton "element content" access path. */
AccessPath element() { result = singleton(any(ElementContent c)) }
/** Gets a singleton property access path. */
AccessPath property(Property p) {
result = singleton(any(PropertyContent c | c.getProperty() = p.getUnboundDeclaration()))
}
/** Gets a singleton field access path. */
AccessPath field(Field f) {
result = singleton(any(FieldContent c | c.getField() = f.getUnboundDeclaration()))
}
/** Gets a singleton synthetic field access path. */
AccessPath synthetic(SyntheticField f) {
result = singleton(any(SyntheticFieldContent c | c.getField() = f))
}
/** Gets an access path representing a property inside a collection. */
AccessPath properties(Property p) { result = TConsAccessPath(any(ElementContent c), property(p)) }
}
/** An unbound callable. */
class SourceDeclarationCallable extends Callable {
SourceDeclarationCallable() { this.isUnboundDeclaration() }
}
/** An unbound method. */
class SourceDeclarationMethod extends SourceDeclarationCallable, Method { }
private newtype TCallableFlowSource =
TCallableFlowSourceQualifier() or
TCallableFlowSourceArg(int i) { i = any(Parameter p).getPosition() } or
TCallableFlowSourceDelegateArg(int i) { hasDelegateArgumentPosition(_, i) }
private predicate hasDelegateArgumentPosition(SourceDeclarationCallable c, int i) {
exists(DelegateType dt |
dt = c.getParameter(i).getType().(SystemLinqExpressions::DelegateExtType).getDelegateType()
|
not dt.getReturnType() instanceof VoidType
)
}
private predicate hasDelegateArgumentPosition2(SourceDeclarationCallable c, int i, int j) {
exists(DelegateType dt |
dt = c.getParameter(i).getType().(SystemLinqExpressions::DelegateExtType).getDelegateType()
|
exists(dt.getParameter(j))
)
}
/** A flow source specification. */
class CallableFlowSource extends TCallableFlowSource {
/** Gets a textual representation of this flow source specification. */
string toString() { none() }
/** Gets the source of flow for call `c`, if any. */
Expr getSource(Call c) { none() }
/**
* Gets the type of the source for call `c`. Unlike `getSource()`, this
* is defined for all flow source specifications.
*/
Type getSourceType(Call c) { result = this.getSource(c).getType() }
}
/** A flow source specification: (method call) qualifier. */
class CallableFlowSourceQualifier extends CallableFlowSource, TCallableFlowSourceQualifier {
override string toString() { result = "qualifier" }
override Expr getSource(Call c) { result = c.getChild(-1) }
}
/** A flow source specification: (method call) argument. */
class CallableFlowSourceArg extends CallableFlowSource, TCallableFlowSourceArg {
private int i;
CallableFlowSourceArg() { this = TCallableFlowSourceArg(i) }
/** Gets the index of this argument. */
int getArgumentIndex() { result = i }
override string toString() { result = "argument " + i }
override Expr getSource(Call c) { result = c.getArgument(i) }
}
/** A flow source specification: output from delegate argument. */
class CallableFlowSourceDelegateArg extends CallableFlowSource, TCallableFlowSourceDelegateArg {
private int i;
CallableFlowSourceDelegateArg() { this = TCallableFlowSourceDelegateArg(i) }
/** Gets the index of this delegate argument. */
int getArgumentIndex() { result = i }
override string toString() { result = "output from argument " + i }
override Expr getSource(Call c) { none() }
override Type getSourceType(Call c) { result = c.getArgument(i).getType() }
}
private newtype TCallableFlowSink =
TCallableFlowSinkQualifier() or
TCallableFlowSinkReturn() or
TCallableFlowSinkArg(int i) { exists(SourceDeclarationCallable c | exists(c.getParameter(i))) } or
TCallableFlowSinkDelegateArg(int i, int j) { hasDelegateArgumentPosition2(_, i, j) }
/** A flow sink specification. */
class CallableFlowSink extends TCallableFlowSink {
/** Gets a textual representation of this flow sink specification. */
string toString() { none() }
/** Gets the sink of flow for call `c`, if any. */
Expr getSink(Call c) { none() }
}
/** A flow sink specification: (method call) qualifier. */
class CallableFlowSinkQualifier extends CallableFlowSink, TCallableFlowSinkQualifier {
override string toString() { result = "qualifier" }
override Expr getSink(Call c) { result = c.getChild(-1) }
}
/** A flow sink specification: return value. */
class CallableFlowSinkReturn extends CallableFlowSink, TCallableFlowSinkReturn {
override string toString() { result = "return" }
override Expr getSink(Call c) { result = c }
}
/** A flow sink specification: (method call) argument. */
class CallableFlowSinkArg extends CallableFlowSink, TCallableFlowSinkArg {
private int i;
CallableFlowSinkArg() { this = TCallableFlowSinkArg(i) }
/** Gets the index of this `out`/`ref` argument. */
int getArgumentIndex() { result = i }
/** Gets the `out`/`ref` argument of method call `mc` matching this specification. */
Expr getArgument(MethodCall mc) {
exists(Parameter p |
p = mc.getTarget().getParameter(i) and
p.isOutOrRef() and
result = mc.getArgumentForParameter(p)
)
}
override string toString() { result = "argument " + i }
override Expr getSink(Call c) {
// The uses of the `i`th argument are the actual sinks
none()
}
}
/** A flow sink specification: parameter of a delegate argument. */
class CallableFlowSinkDelegateArg extends CallableFlowSink, TCallableFlowSinkDelegateArg {
private int delegateIndex;
private int parameterIndex;
CallableFlowSinkDelegateArg() {
this = TCallableFlowSinkDelegateArg(delegateIndex, parameterIndex)
}
/** Gets the index of the delegate argument. */
int getDelegateIndex() { result = delegateIndex }
/** Gets the index of the delegate parameter. */
int getDelegateParameterIndex() { result = parameterIndex }
override string toString() {
result = "parameter " + parameterIndex + " of argument " + delegateIndex
}
override Expr getSink(Call c) {
// The uses of the `j`th parameter are the actual sinks
none()
}
}
/** A specification of data flow for a library (non-source code) type. */
abstract class LibraryTypeDataFlow extends Type {
LibraryTypeDataFlow() { this = this.getUnboundDeclaration() }
/**
* Holds if data may flow from `source` to `sink` when calling callable `c`.
*
* `preservesValue` indicates whether the value from `source` is preserved
* (possibly copied) to `sink`. For example, the value is preserved from `x`
* to `x.ToString()` when `x` is a `string`, but not from `x` to `x.ToLower()`.
*/
pragma[nomagic]
predicate callableFlow(
CallableFlowSource source, CallableFlowSink sink, SourceDeclarationCallable c,
boolean preservesValue
) {
none()
}
/**
* Holds if data may flow from `source` to `sink` when calling callable `c`.
*
* `sourceAp` describes the contents of `source` that flows to `sink`
* (if any), and `sinkAp` describes the contents of `sink` that it
* flows to (if any).
*/
pragma[nomagic]
predicate callableFlow(
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp,
SourceDeclarationCallable c, boolean preservesValue
) {
none()
}
/**
* Holds if the access path obtained by concatenating `head` onto `tail` is
* needed for a summary specified by `callableFlow()`.
*
* This predicate is needed for QL technical reasons only (the IPA type used
* to represent access paths needs to be bounded).
*/
predicate requiresAccessPath(Content head, AccessPath tail) { none() }
/**
* Holds if values stored inside `content` are cleared on objects passed as
* arguments of type `source` to calls that target `callable`.
*/
pragma[nomagic]
predicate clearsContent(
CallableFlowSource source, Content content, SourceDeclarationCallable callable
) {
none()
}
}
/**
* An internal module for translating old `LibraryTypeDataFlow`-style
* flow summaries into the new style.
*/
private module FrameworkDataFlowAdaptor {
private CallableFlowSource toCallableFlowSource(SummaryComponentStack input) {
result = TCallableFlowSourceQualifier() and
input = SummaryComponentStack::qualifier()
or
exists(int i |
result = TCallableFlowSourceArg(i) and
input = SummaryComponentStack::argument(i)
)
or
exists(int i | result = TCallableFlowSourceDelegateArg(i) |
input =
SummaryComponentStack::push(SummaryComponent::return(), SummaryComponentStack::argument(i))
)
}
private CallableFlowSink toCallableFlowSink(SummaryComponentStack output) {
result = TCallableFlowSinkQualifier() and
output = SummaryComponentStack::qualifier()
or
result = TCallableFlowSinkReturn() and
output = SummaryComponentStack::return()
or
exists(int i |
result = TCallableFlowSinkArg(i) and
output = SummaryComponentStack::argument(i)
)
or
exists(int i, int j | result = TCallableFlowSinkDelegateArg(i, j) |
output =
SummaryComponentStack::push(SummaryComponent::parameter(j),
SummaryComponentStack::argument(i))
)
}
private class FrameworkDataFlowAdaptor extends SummarizedCallable {
private LibraryTypeDataFlow ltdf;
FrameworkDataFlowAdaptor() {
ltdf.callableFlow(_, _, this, _) or
ltdf.callableFlow(_, _, _, _, this, _) or
ltdf.clearsContent(_, _, this)
}
predicate input(
CallableFlowSource source, AccessPath sourceAp, SummaryComponent head,
SummaryComponentStack tail, int i
) {
ltdf.callableFlow(source, sourceAp, _, _, this, _) and
source = toCallableFlowSource(tail) and
head = SummaryComponent::content(sourceAp.getHead()) and
i = 0
or
exists(SummaryComponent tailHead, SummaryComponentStack tailTail |
this.input(source, sourceAp, tailHead, tailTail, i - 1) and
head = SummaryComponent::content(sourceAp.drop(i).getHead()) and
tail = SummaryComponentStack::push(tailHead, tailTail)
)
}
predicate output(
CallableFlowSink sink, AccessPath sinkAp, SummaryComponent head, SummaryComponentStack tail,
int i
) {
ltdf.callableFlow(_, _, sink, sinkAp, this, _) and
sink = toCallableFlowSink(tail) and
head = SummaryComponent::content(sinkAp.getHead()) and
i = 0
or
exists(SummaryComponent tailHead, SummaryComponentStack tailTail |
this.output(sink, sinkAp, tailHead, tailTail, i - 1) and
head = SummaryComponent::content(sinkAp.drop(i).getHead()) and
tail = SummaryComponentStack::push(tailHead, tailTail)
)
}
override predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
ltdf.callableFlow(toCallableFlowSource(input), toCallableFlowSink(output), this,
preservesValue)
or
exists(
CallableFlowSource source, AccessPath sourceAp, CallableFlowSink sink, AccessPath sinkAp
|
ltdf.callableFlow(source, sourceAp, sink, sinkAp, this, preservesValue) and
(
exists(SummaryComponent head, SummaryComponentStack tail |
this.input(source, sourceAp, head, tail, sourceAp.length() - 1) and
input = SummaryComponentStack::push(head, tail)
)
or
sourceAp.length() = 0 and
source = toCallableFlowSource(input)
) and
(
exists(SummaryComponent head, SummaryComponentStack tail |
this.output(sink, sinkAp, head, tail, sinkAp.length() - 1) and
output = SummaryComponentStack::push(head, tail)
)
or
sinkAp.length() = 0 and
sink = toCallableFlowSink(output)
)
)
}
override predicate clearsContent(ParameterPosition pos, Content content) {
exists(SummaryComponentStack input |
ltdf.clearsContent(toCallableFlowSource(input), content, this) and
input = SummaryComponentStack::argument(pos.getPosition())
)
}
}
private class AdaptorRequiredSummaryComponentStack extends RequiredSummaryComponentStack {
private SummaryComponent head;
AdaptorRequiredSummaryComponentStack() {
exists(int i |
exists(TCallableFlowSourceDelegateArg(i)) and
head = SummaryComponent::return() and
this = SummaryComponentStack::singleton(SummaryComponent::argument(i))
)
or
exists(int i, int j | exists(TCallableFlowSinkDelegateArg(i, j)) |
head = SummaryComponent::parameter(j) and
this = SummaryComponentStack::singleton(SummaryComponent::argument(i))
)
or
exists(FrameworkDataFlowAdaptor adaptor |
adaptor.input(_, _, head, this, _)
or
adaptor.output(_, _, head, this, _)
)
}
override predicate required(SummaryComponent c) { c = head }
}
}
/** Data flow for `System.Text.StringBuilder`. */
class SystemTextStringBuilderFlow extends LibraryTypeDataFlow, SystemTextStringBuilderClass {
override predicate clearsContent(
CallableFlowSource source, Content content, SourceDeclarationCallable callable
) {
source = TCallableFlowSourceQualifier() and
callable = this.getAMethod("Clear") and
content instanceof ElementContent
}
}
/** Data flow for `System.Collections.IEnumerable` (and sub types). */
class IEnumerableFlow extends LibraryTypeDataFlow, RefType {
IEnumerableFlow() { this.getABaseType*() instanceof SystemCollectionsIEnumerableInterface }
override predicate clearsContent(
CallableFlowSource source, Content content, SourceDeclarationCallable callable
) {
source = TCallableFlowSourceQualifier() and
callable = this.getAMethod("Clear") and
content instanceof ElementContent
}
}
abstract private class SyntheticTaskField extends SyntheticField {
bindingset[this]
SyntheticTaskField() { any() }
override Type getType() { result instanceof SystemThreadingTasksTaskTClass }
}
private class SyntheticTaskAwaiterUnderlyingTaskField extends SyntheticTaskField {
SyntheticTaskAwaiterUnderlyingTaskField() { this = "m_task_task_awaiter" }
}
private class SyntheticConfiguredTaskAwaitableUnderlyingTaskField extends SyntheticTaskField {
SyntheticConfiguredTaskAwaitableUnderlyingTaskField() {
this = "m_task_configured_task_awaitable"
}
}
private class SyntheticConfiguredTaskAwaiterField extends SyntheticField {
SyntheticConfiguredTaskAwaiterField() { this = "m_configuredTaskAwaiter" }
override Type getType() {
result instanceof
SystemRuntimeCompilerServicesConfiguredTaskAwaitableTConfiguredTaskAwaiterStruct
}
}
private predicate recordConstructorFlow(Constructor c, int i, Property p) {
c = any(Record r).getAMember() and
exists(string name |
c.getParameter(i).getName() = name and
c.getDeclaringType().getAMember(name) = p
)
}
private class RecordConstructorFlowRequiredSummaryComponentStack extends RequiredSummaryComponentStack {
private SummaryComponent head;
RecordConstructorFlowRequiredSummaryComponentStack() {
exists(Property p |
recordConstructorFlow(_, _, p) and
head = SummaryComponent::property(p) and
this = SummaryComponentStack::singleton(SummaryComponent::return())
)
}
override predicate required(SummaryComponent c) { c = head }
}
private class RecordConstructorFlow extends SummarizedCallable {
RecordConstructorFlow() { recordConstructorFlow(this, _, _) }
override predicate propagatesFlow(
SummaryComponentStack input, SummaryComponentStack output, boolean preservesValue
) {
exists(int i, Property p |
recordConstructorFlow(this, i, p) and
input = SummaryComponentStack::argument(i) and
output = SummaryComponentStack::propertyOf(p, SummaryComponentStack::return()) and
preservesValue = true
)
}
}

View File

@@ -3,6 +3,7 @@
import csharp
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.dataflow.ExternalFlow
private import semmle.code.csharp.dataflow.FlowSummary
/** The `System.Collections` namespace. */
class SystemCollectionsNamespace extends Namespace {
@@ -45,6 +46,20 @@ private class SystemCollectionIEnumerableFlowModelCsv extends SummaryModelCsv {
}
}
/** Clear content for Clear methods in all subtypes of `System.Collections.IEnumerable`. */
private class SystemCollectionsIEnumerableClearFlow extends SummarizedCallable {
SystemCollectionsIEnumerableClearFlow() {
this.getDeclaringType().(RefType).getABaseType*() instanceof
SystemCollectionsIEnumerableInterface and
this.hasName("Clear")
}
override predicate clearsContent(ParameterPosition pos, DataFlow::Content content) {
pos.getPosition() = -1 and
content instanceof DataFlow::ElementContent
}
}
/** The `System.Collections.IEnumerator` interface. */
class SystemCollectionsIEnumeratorInterface extends SystemCollectionsInterface {
SystemCollectionsIEnumeratorInterface() { this.hasName("IEnumerator") }

View File

@@ -3,6 +3,7 @@
import csharp
private import semmle.code.csharp.frameworks.System
private import semmle.code.csharp.dataflow.ExternalFlow
private import semmle.code.csharp.dataflow.FlowSummary
/** The `System.Text` namespace. */
class SystemTextNamespace extends Namespace {
@@ -25,6 +26,18 @@ class SystemTextStringBuilderClass extends SystemTextClass {
Method getAppendFormatMethod() { result = this.getAMethod("AppendFormat") }
}
/** Clear content for `System.Text.StringBuilder.Clear`. */
private class SystemTextStringBuilderClearFlow extends SummarizedCallable {
SystemTextStringBuilderClearFlow() {
this = any(SystemTextStringBuilderClass s).getAMethod("Clear")
}
override predicate clearsContent(ParameterPosition pos, DataFlow::Content content) {
pos.getPosition() = -1 and
content instanceof DataFlow::ElementContent
}
}
/** Data flow for `System.Text.StringBuilder`. */
private class SystemTextStringBuilderFlowModelCsv extends SummaryModelCsv {
override predicate row(string row) {

View File

@@ -2,6 +2,7 @@
import csharp
private import semmle.code.csharp.frameworks.system.Runtime
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.ExternalFlow
/** The `System.Runtime.CompilerServices` namespace. */
@@ -53,6 +54,15 @@ class SystemRuntimeCompilerServicesConfiguredTaskAwaitableTStruct extends System
}
}
private class SyntheticConfiguredTaskAwaiterField extends SyntheticField {
SyntheticConfiguredTaskAwaiterField() { this = "m_configuredTaskAwaiter" }
override Type getType() {
result instanceof
SystemRuntimeCompilerServicesConfiguredTaskAwaitableTConfiguredTaskAwaiterStruct
}
}
/** Data flow for `System.Runtime.CompilerServices.ConfiguredTaskAwaitable<>`. */
private class SystemRuntimeCompilerServicesConfiguredTaskAwaitableTFlowModelCsv extends SummaryModelCsv {
override predicate row(string row) {

View File

@@ -2,6 +2,7 @@
import csharp
private import semmle.code.csharp.frameworks.system.Threading
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.ExternalFlow
/** The `System.Threading.Tasks` namespace. */
@@ -91,6 +92,23 @@ class SystemThreadingTasksTaskTClass extends SystemThreadingTasksUnboundGenericC
Method getConfigureAwaitMethod() { result = this.getAMethod("ConfigureAwait") }
}
abstract private class SyntheticTaskField extends SyntheticField {
bindingset[this]
SyntheticTaskField() { any() }
override Type getType() { result instanceof SystemThreadingTasksTaskTClass }
}
private class SyntheticTaskAwaiterUnderlyingTaskField extends SyntheticTaskField {
SyntheticTaskAwaiterUnderlyingTaskField() { this = "m_task_task_awaiter" }
}
private class SyntheticConfiguredTaskAwaitableUnderlyingTaskField extends SyntheticTaskField {
SyntheticConfiguredTaskAwaitableUnderlyingTaskField() {
this = "m_task_configured_task_awaitable"
}
}
/** Data flow for `System.Threading.Tasks.Task<>`. */
private class SystemThreadingTasksTaskTFlowModelCsv extends SummaryModelCsv {
override predicate row(string row) {