-
Notifications
You must be signed in to change notification settings - Fork 69
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
Refine backprop assignments in error message #87
Refine backprop assignments in error message #87
Conversation
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## sonalmahajan15/add-deep-copy #87 +/- ##
================================================================
- Coverage 89.48% 89.24% -0.24%
================================================================
Files 54 54
Lines 8865 9087 +222
================================================================
+ Hits 7933 8110 +177
- Misses 775 820 +45
Partials 157 157 ☔ View full report in Codecov by Sentry. |
67d7ac3
to
9c6b9a2
Compare
16bbb5e
to
2069a0f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM 😃 but let's make sure the artifact size does not increase too much since we are adding a good amount of strings.
Maybe test after PR #78 which hopefully will make this a non-issue. (I'll try to merge that stack of PR ASAP, currently it is pending final performance validations)
// Assignment is a struct that represents an assignment to an expression | ||
type Assignment struct { | ||
LHSExprStr string | ||
RHSExprStr string | ||
Position token.Position | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: why not just store *ast.AssignStmt
AST node directly and print whenever needed? We do not have to worry about cross-package independence since the consumer triggers are only used within the analysis of a single package.
Once the consume trigger gets to the inference engine, it will be converted to a string representation and then stored in artifact. So here we do not really have to worry about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What you are suggesting logically makes sense, but there were a couple of reasons why I did not opt for this design choice.
backpropAcrossOneToOneAssignment
which populates theAssignment
struct object, does not have*ast.AssignStmt
, but instead only has LHS/RHSast.Expr
s.- We can of course store
ast.Expr
s here. But since we want to convert theast.Expr
to string for printing, we'll additionally need to also store*analysis.Pass
to facilitate that.
Hence, I thought exposing and storing only the bare minimum objects here might be more desirable. Let me know if you think otherwise. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see!
The only concern I have is the memory consumption and artifact size increases (we are already getting issue reports stating that NilAway consumes memory a lot). Let's
(1) run make build && bin/nilaway -memprofile ./mem.prof std
, then go tool pprof -alloc_space ./mem.pprof
and then type top
, which should show the sum of allocated memory. This should be a good proxy to see the memory consumption. We could simply compare before & after this PR to see if it has a huge impact on memory.
(2) we should run performance validations internally to see if it has an impact on artifact size.
Other than that, this makes sense to me 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair point! I had performed both the checks discussed above: memory profiling and internal performance evaluation. There was a slight increase in memory use, but well within the acceptable bounds. The performance evaluation did not show any noticeable change. In summary, I have confirmed that the changes in this PR do not incur any significant overhead.
util/util.go
Outdated
// ExprToString converts AST expression to string | ||
func ExprToString(e ast.Expr, pass *analysis.Pass) string { | ||
var buf bytes.Buffer | ||
err := printer.Fprint(&buf, pass.Fset, e) | ||
if err != nil { | ||
panic(fmt.Sprintf("Failed to convert AST expression to string: %v\n", err)) | ||
} | ||
return buf.String() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having dejavu of this, didn't we add this in some other PRs?
Anyways, not something to fix :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haha, yes. This function was introduced in the revised error message format PR at one point, but removed later since it did not have any use. So, it was re-added in this PR since we want to convert LHS and RHS ast.Expr
in an assignment statement to string.
// | ||
// nilable(path, result 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
// TriggerIfNonNil is triggered if the contained Annotation is non-nil | ||
type TriggerIfNonNil struct { | ||
Ann Key | ||
assignmentFlow |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Question: do we need to care about this field in equals
and Copy
methods?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now, with using orderedmap
for assignmentFlow
, we should consider it in copy
. Added code for it.
For equals
we should not account for it. Consider the situation of multiple nilable flows reach a dereference site. We keep NilAway practical by tracking only one representative nil flow, and printing only one error message for the dereference site with one representative flow printed in the error message. However, considering assignmentFlow
in equals
would mean tracking all nilable flows in separate full triggers and reporting errors through all paths. This would likely incur a performance penalty, which we wouldn't want. I have added a note at the definition of assignmentFlow
explaining this.
annotation/consume_trigger.go
Outdated
// backprop algorithm populates assignment entries in backward order. Reverse entries to get forward order of assignments. | ||
for i, j := 0, len(entries)-1; i < j; i, j = i+1, j-1 { | ||
entries[i], entries[j] = entries[j], entries[i] | ||
} | ||
|
||
// build string slice | ||
strs := make([]string, len(entries)) | ||
for i, entry := range entries { | ||
strs[i] = entry.String() | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just iterate in reverse order instead of actually reversing the slice?
for i := len(entries) - 1; i > 0; i-- {
entries[i]
//...
}}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. Done in the context of using orderedmap
.
} | ||
return fmt.Sprintf("found in at least one path of `%s()` for return in position %d", u.FuncName, u.RetNum) | ||
sb.WriteString(u.AssignmentStr) | ||
return sb.String() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having read through this all, I'm actually a bit tempted to create a shared struct, say metaPrestring
or something similar that stores AssignmentStr
since it is shared by all of the prestring nodes. Then we just embed this struct in all Prestring structs.
Then, this metaPrestring
can expose two methods, writePrefix(io.Writer)
and writeSuffix(io.Writer)
, which is called at the beginning and at the end of each String()
function (by u.meta.writePrefix(sb)
, since strings.Builder
implements the io.Writer
interface).
This would allow future expansion a little easier.
But I also see that might be an overkill for now, so we can keep this in mind until we need to add another field to all prestrings :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally agreed! Every time I visit consume_trigger.go
and produce_trigger.go
, I am tempted to rearchitect it for Prestrings. I feel there is a lot of repetition there. A problem to be solved some other day :)
annotation/consume_trigger.go
Outdated
// fix point convergence in backprop could mean duplicate entries in this slice, hence we filter out the duplicates here | ||
entries := make([]Assignment, 0, len(a.assignments)) | ||
seen := make(map[string]bool) | ||
for _, entry := range a.assignments { | ||
if !seen[entry.String()] { | ||
seen[entry.String()] = true | ||
entries = append(entries, entry) | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use our own orderedmap
implementation after PR #66 is merged (I'll try to merge that ASAP) such that we do not have to store a lot of entries and then dedup (i.e., we dedup on the fly).
But taking a step back, would this cause a lot of performance penalty? are there any other ways (maybe not) 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Using orderedmap
now :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Haven't observed any performance penalty as such, but will run a thorough performance experiment to confirm.
// TODO: below check for `lhsNode != nil` should not be needed when NilAway supports Ok form for | ||
// used-defined functions (tracked issue #77) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
(BTW this function is long enough to the point it's a bit painful to (re-)read every time there is a change to it, we probably want to refactor it to smaller pieces in future revisions).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yes, agreed. This can definitely benefit from refactoring :)
} | ||
|
||
func test2(x *int) { | ||
x = nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we also add cases where x
is
(1) read from a map without guarding (default nilable)
(2) reading from a nil channel perhaps?
(3) an empty slice
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added.
afterLastIndex := len(rootNode.triggers) | ||
|
||
// Update consumers of newly added triggers with assignment entries for informative printing of errors | ||
if len(rootNode.triggers) > beforeLastIndex && len(rootNode.triggers) <= afterLastIndex { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we need this check?
(1) will len(rootNode.triggers) > beforeLastIndex
ever be false
? i.e., will rootNode.triggers
actually shrink?
(2) len(rootNode.triggers) == afterLastIndex
here, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, revised the checks.
2069a0f
to
bba340e
Compare
9c6b9a2
to
04aa902
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Logic LGTM! The only concern I have is memory consumption & artifact size increases.
Let's do a quick profiling & internal performance validations to ensure the consumptions are still within reasonable bounds 😃
// Assignment is a struct that represents an assignment to an expression | ||
type Assignment struct { | ||
LHSExprStr string | ||
RHSExprStr string | ||
Position token.Position | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see!
The only concern I have is the memory consumption and artifact size increases (we are already getting issue reports stating that NilAway consumes memory a lot). Let's
(1) run make build && bin/nilaway -memprofile ./mem.prof std
, then go tool pprof -alloc_space ./mem.pprof
and then type top
, which should show the sum of allocated memory. This should be a good proxy to see the memory consumption. We could simply compare before & after this PR to see if it has a huge impact on memory.
(2) we should run performance validations internally to see if it has an impact on artifact size.
Other than that, this makes sense to me 👍
a84f41a
to
3f45765
Compare
1c8d6f6
to
7614ccb
Compare
3f45765
to
6726b23
Compare
7614ccb
to
d97e64e
Compare
6726b23
to
1c82754
Compare
1c82754
to
06d3eb8
Compare
d97e64e
to
650ff29
Compare
Checked with profiling and internal performance validation. Both memory consumption and artifact size within acceptable bounds. |
650ff29
to
a1668d2
Compare
92d792c
to
43b26d7
Compare
This PR shortens the expression strings in assignment part of the error messages. For example, the expression `x = s.foo(longVarName, &anotherLongVarName, "abc", true)` is shortened to `s.foo(...)` to offer better readability. The changes have also have been performance evaluated internally.
This PR adds assignment tracking for many-to-one assignments for printing informative error messages, following suit of one-to-one assignment tracking (PR #87 ).
This PR refines the error message by incorporating information about the assignments. Following is an example of an informative error message:
Code snippet:
Error message:
[closes #83 ]
[depends on #86 ]