AdvocateGo is an analysis tool for Go programs.
It detects concurrency bugs and gives diagnostic insight.
This is achieved through happens-before-relation
and vector-clocks
Furthermore it is also able to produce traces which can be fed back into the program in order to experience the predicted bug.
A more in detail explanation of how it works can be found here.
Simplified flowchart of the AdvocateGo Process
For more detail see this in depth diagram
These steps can also be done automatically with scripts. If you want to know more about using them you can skip straight to the Tooling section. Doing these steps manually at least once is still encouraged to get a feel for how advocateGo works.
You need to adjust the main method or unit test you want to analyze slightly in order to analyze them. The code snippet you need is
import "advocate"
...
// ======= Preamble Start =======
advocate.InitTracing(0)
defer advocate.Finish()
// ======= Preamble End =======
...
Eg. like this
import "advocate"
func main(){
// ======= Preamble Start =======
advocate.InitTracing(0)
defer advocate.Finish()
// ======= Preamble End =======
...
}
or like this for a unit test
import "advocate"
...
func TestImportantThings(t *testing.T){
// ======= Preamble Start =======
advocate.InitTracing(0)
defer advocate.Finish()
// ======= Preamble End =======
...
}
Before your newly updated main method or test you will need to build the AdvocateGo-Runtime. This can be done easily by running
./src/make.bash
./src/make.bat
Lastly you need to set the goroot-environment-variable like so
export GOROOT=$HOME/ADVOCATE/go-patch/
Now you can finally run your go program with the binary that you build in Step 1
.
It is located under ./go-patch/bin/go
Eg. like so
./go-patch/bin/go run main.go
or like this for your tests
./go-patch/bin/go test
After you run your program you will find that it generated the folder advocateTrace
.
If you are curious about the structure of said trace, you can find an in depth explanation here
It contains a record of what operation ran in what thread during the execution of your program.
This acts as input for the analyzer located under ./analyzer/analyzer
.
It can be run like so
./analyzer/analyzer -t advocateTrace
Running the analyzer will generate 3 files for you
- machine_readable.log (good for parsing and further analysis)
- human readable.log (more readable representation of bug predictions)
- rewritten_Trace (a trace in which the bug it was rewritten for would occur)
A more detailed explanation of the file contents can be found under AnalysisResult.md
AdvocateGo currently supports following bugs
- A1: Send on closed channel
- A2: Receive on closed channel
- A3: Close on closed channel
- A4: Concurrent recv
- A5: Select case without partner
- P1: Possible send on closed channel
- P2: Possible receive on closed channel
- P3: Possible negative waitgroup counter
- L1: Leak on unbuffered channel with possible partner
- L2: Leak on unbuffered channel without possible partner
- L3: Leak on buffered channel with possible partner
- L4: Leak on buffered channel without possible partner
- L5: Leak on nil channel
- L6: Leak on select with possible partner
- L7: Leak on select without possible partner
- L8: Leak on mutex
- L9: Leak on waitgroup
- L0: Leak on cond
This process is similar to when we first ran the program. Only the Overhead changes slightly.
Instead want to use this overhead
// ======= Preamble Start =======
advocate.EnableReplay(n)
defer advocate.WaitForReplayFinish()
// ======= Preamble End =======
where the variable n
is the rewritten trace you want to use.
Note that the method looks for the rewritten_trace
folder in the same directory as the file is located
A more detailed description of how replays work and a list of what bugs are currently supported for replay can be found under TraceReplay.md and TraceReconstruciton.md.
There are certain scripts that will come in handy when working with AdvocateGo
There are scripts that automatically add and remove the overhead described in Step 1
Main overhead inserter takes a single file as an argument. It will insert the overhead right at the start of main and manage the imports.
It throws an error if no main method is present.
Likewise main overhead remover will remove the overhead
Unit test overhead inserter additionally requires the test name you want to apply the overhead to. Apart from that it works just like with main method overhead inserter
Likewise overhead remover will remove the overhead if is present.
runFullWorkflowOnMain.bash accepts a single go file containing a main method automatically runs the analysis + replay on all unit tests. After running you will additionally get a csv file that lists all predicted and confirmed bugs. (ongoing)
Its result and additional information (rewritten traces, logs, etc) will be written to. advocateResult
runFullWorkflowOnAllUnitTests.bash takes an entire project and automatically runs the analysis + replay on all unit tests. After running you will additionally get a csv file that lists all predicted and confirmed bugs. (ongoing)
Its result and additional information (rewritten traces, logs, etc) will be written to. advocateResult
After analyzing you can evaluate your advocateResult
folder with generateStatistics.go. It will provide following information.
- Overview of predicted bugs
- Overview of expected exit codes (after rewrite)
- Overview of actual exit codes that appeared after running the reordered programs
It is the users responsibility of the user to make sure, that the input to the program, including e.g. API calls are equal for the recording and the tracing. Otherwise the replay is likely to get stuck.
Do not change the program code between trace recording and replay. The identification of the operations is based on the file names and lines, where the operations occur. If they get changed, the program will most likely block without terminating. If you need to change the program, you must either rerun the trace recording or change the effected trace elements in the recorded trace. This also includes the adding of the replay header. Make sure, that it is already in the program (but commented out), when you run the recording.