-
Notifications
You must be signed in to change notification settings - Fork 1.9k
C++: Initial IR-based dataflow implementation #597
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
adityasharad
merged 15 commits into
github:rc/1.19
from
dave-bartolomeo:dave/IRDataflow
Dec 11, 2018
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
58f7596
C++: IR-based dataflow
dave-bartolomeo ae8f18c
C++: Treat all `Convert` instructions as dataflow
dave-bartolomeo af44356
C++: Fix handling of accesses to escaped variables in Aliased SSA
dave-bartolomeo 309b703
C++: Models for side-effect-free functions
dave-bartolomeo 7eb47f3
C++: A few more IR dataflow tweaks
dave-bartolomeo 2822d14
C++: Add missing changes to test_ir.expected
dave-bartolomeo e11b4b6
C++: Fix IR Dataflow PR feedback
dave-bartolomeo 65360b2
C++: Change model API based on feedback
dave-bartolomeo e8efb32
C++: Remove `StoreDestinationAsPostUpdateNode`
dave-bartolomeo 2b80aee
C++: Use `getConvertedResultExpr` in IR-based dataflow
dave-bartolomeo 84b39bf
C++: Simplify models for side effects and alias info.
dave-bartolomeo ebbd701
C++: Fix PR feedback
dave-bartolomeo df882a9
C++: Avoid creating `ExprNode`s for `Conversion`s
dave-bartolomeo 2399371
Revert "C++: Avoid creating `ExprNode`s for `Conversion`s"
dave-bartolomeo 78e5b3a
C++: Add IR dataflow to ImportAdditionalQueries.ql
dave-bartolomeo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,55 +1,76 @@ | ||
| { | ||
| "C++ IR Instruction": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll" | ||
| ], | ||
| "C++ IR IRBlock": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRBlock.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll" | ||
| ], | ||
| "C++ IR IRVariable": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll" | ||
| ], | ||
| "C++ IR FunctionIR": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/FunctionIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/FunctionIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/FunctionIR.qll" | ||
| ], | ||
| "C++ IR Operand": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll" | ||
| ], | ||
| "C++ IR IRImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll" | ||
| ], | ||
| "C++ IR IRSanityImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll" | ||
| ], | ||
| "C++ IR PrintIRImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll" | ||
| ], | ||
| "C++ SSA AliasAnalysis": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll" | ||
| ], | ||
| "C++ SSA SSAConstruction": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll" | ||
| ], | ||
| "C++ IR ValueNumber": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll" | ||
| ] | ||
| "DataFlow Java/C++": [ | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl.qll", | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl2.qll", | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl3.qll", | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl4.qll", | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImpl5.qll", | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplDepr.qll", | ||
| "cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl.qll", | ||
| "cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl2.qll", | ||
| "cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl3.qll", | ||
| "cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImpl4.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl2.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl3.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImpl4.qll" | ||
| ], | ||
| "DataFlow Java/C++ Common": [ | ||
| "java/ql/src/semmle/code/java/dataflow/internal/DataFlowImplCommon.qll", | ||
| "cpp/ql/src/semmle/code/cpp/dataflow/internal/DataFlowImplCommon.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowImplCommon.qll" | ||
| ], | ||
| "C++ IR Instruction": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Instruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Instruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Instruction.qll" | ||
| ], | ||
| "C++ IR IRBlock": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRBlock.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRBlock.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRBlock.qll" | ||
| ], | ||
| "C++ IR IRVariable": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRVariable.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRVariable.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRVariable.qll" | ||
| ], | ||
| "C++ IR FunctionIR": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/FunctionIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/FunctionIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/FunctionIR.qll" | ||
| ], | ||
| "C++ IR Operand": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/Operand.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/Operand.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/Operand.qll" | ||
| ], | ||
| "C++ IR IRImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IR.qll" | ||
| ], | ||
| "C++ IR IRSanityImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/IRSanity.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/IRSanity.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/IRSanity.qll" | ||
| ], | ||
| "C++ IR PrintIRImpl": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/PrintIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/PrintIR.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/PrintIR.qll" | ||
| ], | ||
| "C++ SSA AliasAnalysis": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/AliasAnalysis.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/AliasAnalysis.qll" | ||
| ], | ||
| "C++ SSA SSAConstruction": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/internal/SSAConstruction.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/internal/SSAConstruction.qll" | ||
| ], | ||
| "C++ IR ValueNumber": [ | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/raw/gvn/ValueNumbering.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/unaliased_ssa/gvn/ValueNumbering.qll", | ||
| "cpp/ql/src/semmle/code/cpp/ir/implementation/aliased_ssa/gvn/ValueNumbering.qll" | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| /** | ||
| * Provides a library for local (intra-procedural) and global (inter-procedural) | ||
| * data flow analysis: deciding whether data can flow from a _source_ to a | ||
| * _sink_. This library differs from the one in `semmle.code.cpp.dataflow` in that | ||
| * this library uses the IR (Intermediate Representation) library, which provides | ||
| * a more precise semantic representation of the program, whereas the other dataflow | ||
| * library uses the more syntax-oriented ASTs. This library should provide more accurate | ||
| * results than the AST-based library in most scenarios. | ||
| * | ||
| * Unless configured otherwise, _flow_ means that the exact value of | ||
| * the source may reach the sink. We do not track flow across pointer | ||
| * dereferences or array indexing. | ||
| * | ||
| * To use global (interprocedural) data flow, extend the class | ||
| * `DataFlow::Configuration` as documented on that class. To use local | ||
| * (intraprocedural) data flow, invoke `DataFlow::localFlow` or | ||
| * `DataFlow::LocalFlowStep` with arguments of type `DataFlow::Node`. | ||
| */ | ||
| import cpp | ||
|
|
||
| module DataFlow { | ||
| import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * Provides a `DataFlow2` module, which is a copy of the `DataFlow` module. Use | ||
| * this class when data-flow configurations must depend on each other. Two | ||
| * classes extending `DataFlow::Configuration` should never depend on each | ||
| * other, but one of them should instead depend on a | ||
| * `DataFlow2::Configuration`, a `DataFlow3::Configuration`, or a | ||
| * `DataFlow4::Configuration`. | ||
| * | ||
| * See `semmle.code.cpp.dataflow.DataFlow` for the full documentation. | ||
| */ | ||
| import cpp | ||
|
|
||
| module DataFlow2 { | ||
| import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl2 | ||
|
|
||
| /** | ||
| * This class exists to prevent mutual recursion between the user-overridden | ||
| * member predicates of `Configuration` and the rest of the data-flow library. | ||
| * Good performance cannot be guaranteed in the presence of such recursion, so | ||
| * it should be replaced by using more than one copy of the data flow library. | ||
| * Four copies are available: `DataFlow` through `DataFlow4`. | ||
| */ | ||
| private abstract | ||
| class ConfigurationRecursionPrevention extends Configuration { | ||
| bindingset[this] | ||
| ConfigurationRecursionPrevention() { any() } | ||
|
|
||
| override predicate hasFlow(Node source, Node sink) { | ||
| strictcount(Node n | this.isSource(n)) < 0 | ||
| or | ||
| strictcount(Node n | this.isSink(n)) < 0 | ||
| or | ||
| strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 | ||
| or | ||
| super.hasFlow(source, sink) | ||
| } | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * Provides a `DataFlow3` module, which is a copy of the `DataFlow` module. Use | ||
| * this class when data-flow configurations must depend on each other. Two | ||
| * classes extending `DataFlow::Configuration` should never depend on each | ||
| * other, but one of them should instead depend on a | ||
| * `DataFlow2::Configuration`, a `DataFlow3::Configuration`, or a | ||
| * `DataFlow4::Configuration`. | ||
| * | ||
| * See `semmle.code.cpp.dataflow.DataFlow` for the full documentation. | ||
| */ | ||
| import cpp | ||
|
|
||
| module DataFlow3 { | ||
| import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl3 | ||
|
|
||
| /** | ||
| * This class exists to prevent mutual recursion between the user-overridden | ||
| * member predicates of `Configuration` and the rest of the data-flow library. | ||
| * Good performance cannot be guaranteed in the presence of such recursion, so | ||
| * it should be replaced by using more than one copy of the data flow library. | ||
| * Four copies are available: `DataFlow` through `DataFlow4`. | ||
| */ | ||
| private abstract | ||
| class ConfigurationRecursionPrevention extends Configuration { | ||
| bindingset[this] | ||
| ConfigurationRecursionPrevention() { any() } | ||
|
|
||
| override predicate hasFlow(Node source, Node sink) { | ||
| strictcount(Node n | this.isSource(n)) < 0 | ||
| or | ||
| strictcount(Node n | this.isSink(n)) < 0 | ||
| or | ||
| strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 | ||
| or | ||
| super.hasFlow(source, sink) | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /** | ||
| * Provides a `DataFlow4` module, which is a copy of the `DataFlow` module. Use | ||
| * this class when data-flow configurations must depend on each other. Two | ||
| * classes extending `DataFlow::Configuration` should never depend on each | ||
| * other, but one of them should instead depend on a | ||
| * `DataFlow2::Configuration`, a `DataFlow3::Configuration`, or a | ||
| * `DataFlow4::Configuration`. | ||
| * | ||
| * See `semmle.code.cpp.dataflow.DataFlow` for the full documentation. | ||
| */ | ||
| import cpp | ||
|
|
||
| module DataFlow4 { | ||
| import semmle.code.cpp.ir.dataflow.internal.DataFlowImpl4 | ||
|
|
||
| /** | ||
| * This class exists to prevent mutual recursion between the user-overridden | ||
| * member predicates of `Configuration` and the rest of the data-flow library. | ||
| * Good performance cannot be guaranteed in the presence of such recursion, so | ||
| * it should be replaced by using more than one copy of the data flow library. | ||
| * Four copies are available: `DataFlow` through `DataFlow4`. | ||
| */ | ||
| private abstract | ||
| class ConfigurationRecursionPrevention extends Configuration { | ||
| bindingset[this] | ||
| ConfigurationRecursionPrevention() { any() } | ||
|
|
||
| override predicate hasFlow(Node source, Node sink) { | ||
| strictcount(Node n | this.isSource(n)) < 0 | ||
| or | ||
| strictcount(Node n | this.isSink(n)) < 0 | ||
| or | ||
| strictcount(Node n1, Node n2 | this.isAdditionalFlowStep(n1, n2)) < 0 | ||
| or | ||
| super.hasFlow(source, sink) | ||
| } | ||
| } | ||
| } |
73 changes: 73 additions & 0 deletions
73
cpp/ql/src/semmle/code/cpp/ir/dataflow/internal/DataFlowDispatch.qll
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| private import cpp | ||
| private import DataFlowPrivate | ||
|
|
||
| Function viableImpl(MethodAccess ma) { | ||
| result = ma.getTarget() | ||
| } | ||
|
|
||
| Function viableCallable(Call call) { | ||
| result = call.getTarget() | ||
| } | ||
|
|
||
| /** | ||
| * Holds if the call context `ctx` reduces the set of viable dispatch | ||
| * targets of `ma` in `c`. | ||
| */ | ||
| predicate reducedViableImplInCallContext(MethodAccess ma, Callable c, Call ctx) { | ||
| none() | ||
| } | ||
|
|
||
| /** | ||
| * Gets a viable dispatch target of `ma` in the context `ctx`. This is | ||
| * restricted to those `ma`s for which a context might make a difference. | ||
| */ | ||
| private Method viableImplInCallContext(MethodAccess ma, Call ctx) { | ||
| // stub implementation | ||
| result = viableImpl(ma) and | ||
| viableCallable(ctx) = ma.getEnclosingFunction() | ||
| } | ||
|
|
||
| /** | ||
| * Gets a viable dispatch target of `ma` in the context `ctx`. This is | ||
| * restricted to those `ma`s for which the context makes a difference. | ||
| */ | ||
| Method prunedViableImplInCallContext(MethodAccess ma, Call ctx) { | ||
| result = viableImplInCallContext(ma, ctx) and | ||
| reducedViableImplInCallContext(ma, _, ctx) | ||
| } | ||
|
|
||
| /** | ||
| * Holds if data might flow from `ma` to a return statement in some | ||
| * configuration. | ||
| */ | ||
| private predicate maybeChainedReturn(MethodAccess ma) { | ||
| exists(ReturnStmt ret | | ||
| exists(ret.getExpr()) and | ||
| ret.getEnclosingFunction() = ma.getEnclosingFunction() and | ||
| not ma.getParent() instanceof ExprStmt | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * Holds if flow returning from `m` to `ma` might return further and if | ||
| * this path restricts the set of call sites that can be returned to. | ||
| */ | ||
| predicate reducedViableImplInReturn(Method m, MethodAccess ma) { | ||
| exists(int tgts, int ctxtgts | | ||
| m = viableImpl(ma) and | ||
| ctxtgts = count(Call ctx | m = viableImplInCallContext(ma, ctx)) and | ||
| tgts = strictcount(Call ctx | viableCallable(ctx) = ma.getEnclosingFunction()) and | ||
| ctxtgts < tgts | ||
| ) and | ||
| maybeChainedReturn(ma) | ||
| } | ||
|
|
||
| /** | ||
| * Gets a viable dispatch target of `ma` in the context `ctx`. This is | ||
| * restricted to those `ma`s and results for which the return flow from the | ||
| * result to `ma` restricts the possible context `ctx`. | ||
| */ | ||
| Method prunedViableImplInCallContextReverse(MethodAccess ma, Call ctx) { | ||
| result = viableImplInCallContext(ma, ctx) and | ||
| reducedViableImplInReturn(result, ma) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.