Repository: nickng/dingo-hunter Branch: master Commit: 6b8cec3641dc Files: 171 Total size: 397.9 KB Directory structure: gitextract_zp5j09dq/ ├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── README.md ├── cfsmextract/ │ ├── cfsmextract.go │ ├── func.go │ ├── gortn.go │ ├── sesstype/ │ │ ├── cfsm.go │ │ ├── dot.go │ │ ├── nodes.go │ │ ├── nodestack.go │ │ ├── nodestack_test.go │ │ ├── op_string.go │ │ ├── sesstype.go │ │ └── sesstype_test.go │ ├── utils/ │ │ ├── defs.go │ │ └── emptyvalue.go │ ├── utils.go │ └── visit.go ├── cmd/ │ ├── buildssa.go │ ├── cfsms.go │ ├── checkfair.go │ ├── migo.go │ ├── root.go │ └── serve.go ├── doc.go ├── examples/ │ ├── altbit/ │ │ └── main.go │ ├── branch-dependent-deadlock/ │ │ └── main.go │ ├── channel-scoping-test/ │ │ └── main.go │ ├── commaok/ │ │ └── main.go │ ├── cond-recur/ │ │ └── main.go │ ├── deadlocking-philosophers/ │ │ └── main.go │ ├── dining-philosophers/ │ │ └── main.go │ ├── factorial/ │ │ └── main.go │ ├── fanin-pattern/ │ │ └── main.go │ ├── fanin-pattern-commaok/ │ │ └── main.go │ ├── fcall/ │ │ └── main.go │ ├── forselect/ │ │ └── main.go │ ├── giachino-concur14-dining-philosopher/ │ │ └── main.go │ ├── giachino-concur14-factorial/ │ │ └── main.go │ ├── github-golang-go-issue-12734/ │ │ └── main.go │ ├── golang-blog-prime-sieve/ │ │ └── main.go │ ├── infinite-prime-sieve/ │ │ └── main.go │ ├── issue-10-close-wrong-migo-chan-name/ │ │ └── main.go │ ├── issue-11-non-communicating-fn-call/ │ │ └── main.go │ ├── jobsched/ │ │ └── main.go │ ├── local-deadlock/ │ │ └── main.go │ ├── local-deadlock-fixed/ │ │ └── main.go │ ├── loop-variations/ │ │ └── main.go │ ├── makechan-in-loop/ │ │ └── main.go │ ├── md5/ │ │ └── main.go │ ├── multi-makechan-same-var/ │ │ └── main.go │ ├── multiple-files/ │ │ ├── main.go │ │ └── x.go │ ├── multiple-timeout/ │ │ └── main.go │ ├── parallel-buffered-recursive-fibonacci/ │ │ └── main.go │ ├── parallel-recursive-fibonacci/ │ │ └── main.go │ ├── parallel-twoprocess-fibonacci/ │ │ └── main.go │ ├── philo/ │ │ └── main.go │ ├── popl17/ │ │ ├── alt-bit/ │ │ │ └── main.go │ │ ├── concsys/ │ │ │ └── main.go │ │ ├── cond-recur/ │ │ │ └── main.go │ │ ├── dinephil/ │ │ │ └── main.go │ │ ├── fact/ │ │ │ └── main.go │ │ ├── fanin/ │ │ │ └── main.go │ │ ├── fanin-alt/ │ │ │ └── main.go │ │ ├── fib/ │ │ │ └── main.go │ │ ├── fib-async/ │ │ │ └── main.go │ │ ├── fixed/ │ │ │ └── main.go │ │ ├── forselect/ │ │ │ └── main.go │ │ ├── jobsched/ │ │ │ └── main.go │ │ ├── mismatch/ │ │ │ └── main.go │ │ └── sieve/ │ │ └── main.go │ ├── popl17ae/ │ │ ├── close/ │ │ │ └── main.go │ │ └── emptyselect/ │ │ └── main.go │ ├── powsers/ │ │ └── powser1.go │ ├── producer-consumer/ │ │ └── main.go │ ├── ring-pattern/ │ │ └── main.go │ ├── russ-cox-fizzbuzz/ │ │ └── main.go │ ├── select-with-continuation/ │ │ └── main.go │ ├── select-with-weak-mismatch/ │ │ └── main.go │ ├── semaphores/ │ │ └── main.go │ ├── send-recv-with-interfaces/ │ │ └── main.go │ ├── simple/ │ │ └── main.go │ ├── single-gortn-method-call/ │ │ └── main.go │ ├── spawn-in-choice/ │ │ └── main.go │ ├── squaring-cancellation/ │ │ └── main.go │ ├── squaring-fanin/ │ │ └── main.go │ ├── squaring-fanin-bad/ │ │ └── main.go │ ├── squaring-pipeline/ │ │ └── main.go │ ├── struct-done-channel/ │ │ └── main.go │ └── timeout-behaviour/ │ └── main.go ├── fairness/ │ └── fairness.go ├── go.mod ├── go.sum ├── logwriter/ │ └── logwriter.go ├── main.go ├── migoextract/ │ ├── call.go │ ├── chan.go │ ├── closure.go │ ├── commaok.go │ ├── context.go │ ├── datastructure.go │ ├── error.go │ ├── instance.go │ ├── loop.go │ ├── loopbound_string.go │ ├── loopstack.go │ ├── loopstate_string.go │ ├── migoextract.go │ ├── phi.go │ ├── pointer.go │ ├── print.go │ ├── select.go │ ├── storage.go │ ├── tuple.go │ └── visit.go ├── ssabuilder/ │ ├── callgraph/ │ │ └── callgraph.go │ ├── channel.go │ ├── errors.go │ ├── pointer.go │ ├── print.go │ ├── ssabuild.go │ └── util.go ├── static/ │ ├── script.js │ └── style.css ├── talks/ │ ├── basic/ │ │ ├── concurrency.go │ │ ├── fanin.go │ │ └── select.go │ ├── conf-cc-2016/ │ │ ├── deadlock-global.html │ │ ├── fanin-global.html │ │ ├── select.html │ │ ├── ssa.html │ │ └── type-inference.html │ ├── conf-cc-2016.slide │ ├── deadlock/ │ │ └── deadlock.go │ ├── snippet/ │ │ └── proc.txt │ ├── static/ │ │ ├── article.css │ │ ├── dir.css │ │ ├── dir.js │ │ ├── jquery-ui.js │ │ ├── slides.js │ │ └── styles.css │ ├── talk-golanguk-2016/ │ │ ├── calculi.html │ │ ├── compat.html │ │ ├── datatype.go │ │ ├── deadlock-notdetected.go │ │ ├── deadlock.go │ │ ├── main-type.html │ │ ├── proc.txt │ │ ├── receiver-type.html │ │ ├── sender-type.html │ │ ├── video.html │ │ └── work-type.html │ ├── talk-golanguk-2016.slide │ ├── talk-jan-2016.slide │ └── templates/ │ ├── action.tmpl │ ├── article.tmpl │ ├── dir.tmpl │ └── slides.tmpl ├── templates/ │ └── index.tmpl └── webservice/ ├── cfsm.go ├── errors.go ├── gong.go ├── handlers.go ├── migo.go ├── play.go ├── server.go ├── ssa.go └── synthesis.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.dot *.pdf *_cfsms dingo-hunter .*~ ================================================ FILE: .gitmodules ================================================ [submodule "third_party/gmc-synthesis"] branch = dingo path = third_party/gmc-synthesis url = https://bitbucket.org/nickng/gmc-synthesis ================================================ FILE: .travis.yml ================================================ language: go script: - go test -v ./... addons: ssh_known_hosts: - bitbucket.org notifications: slack: secure: tVChzqfZMyTmdTiiVf74gNVYeG6N6ceOzU5v4fKMQhGHyXQK74SSa3+9La1fQpcO7VGduSgtvGtFN2FOY4JfqKLY7v5y93RFOBdgGOVrys5WWY2vXHKS9pHbwjIHOTmdaWvkj9i3jR0g2FiCKt1sq2LuE9Qw9qOYFN4PiZzm8NP0CM7GKhFJ1emJ8nrQMJRwA6F7LvCizaN4p0XMDCNe31WpHecAaZVivtR4JA/1r4ufRI7yD2a6B3sk8pePpCBoKm3PKDEiuSLf1n4GbBmCI4iZbOZNZ+4tIFdLJnJQc520YIHJ/DTJZo6MqDeEotod0yIQ60jIWNwrxqfYXcZ1GcLPwAe5j5GKKWnIs5JH/tPB3Le8r5RwzJ+SXPMv8XTQQ/q5PmrhRCxOVLXS/Au+1KbooJrEyYG6etiP97ucXjmcKLL4f+JKwIhBHUNNwuKTO30s0dHJO66LINd/ncO89kUyYKV6ijm8S/4BEMvZaKoJJhjnNbjUXfX6jrG20DEDNl2TvAgeZm6/LeMQ/JObdYV35+IXXyB9Ns/obs6f4+3n/XM/xQA/apOhbRPX3RUQNIcSMcyH1Mwuud3ehsMXQ298bl2t/w0x2swL7UzcNj6SFppmb1VtKJYStPtKj9wbjy8E/y7qlxYMZZSsGXeOHlUu/rtnAD6zpAKi2VJdPRY= ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # dingo-hunter [![Build Status](https://travis-ci.org/nickng/dingo-hunter.svg?branch=master)](https://travis-ci.org/nickng/dingo-hunter) ## Static analyser for finding Deadlocks in Go This is a static analyser to model concurrency and find deadlocks in Go code. The main purpose of this tool is to infer from Go source code its concurrency model in either of the two formats: - Communicating Finite State Machines (CFSMs) - MiGo types The inferred models are then passed to separate tools for formal analysis. In both approaches, we apply a system known in the literature as [**Session Types**](http://mrg.doc.ic.ac.uk/publications/multiparty-asynchronous-session-types/) to look for potential communication mismatches to preempt potential deadlocks. ## Install `dingo-hunter` can be installed by `go get`, go version `go1.7.1` is required. $ go get -u github.com/nickng/dingo-hunter ## Usage There are two approaches (CFSMs and MiGo types) based on two research work. ### CFSMs approach This approach generates CFSMs as models for goroutines spawned in the program, the CFSMs are then passed to a synthesis tool to construct a global choreography and check for validity (See [paper][cc16]). First install the synthesis tool `gmc-synthesis` by checking out the submodule: $ cd $GOPATH/src/github.com/nickng/dingo-hunter; git submodule init; git submodule update $ cd third_party/gmc-synthesis Follow `README` to install and build `gmc-synthesis`, i.e. $ cabal install MissingH split Graphalyze $ ./getpetrify # and install to /usr/local/ or in $PATH $ ghc -threaded GMC.hs --make && ghc --make BuildGlobal To run CFSMs generation on `example/local-deadlock/main.go`: $ dingo-hunter cfsms --prefix deadlock example/local-deadlock/main.go Output should say 2 channels, then run synthesis tool on extracted CFSMs $ cd third_party/gmc-synthesis $ ./runsmc inputs/deadlock_cfsms 2 # where 2 is the number of channels The `SMC check` line indicates if the global graph satisfies SMC (i.e. safe) or not. #### Limitations * Our tool currently support synchronous (unbuffered channel) communication only * Goroutines spawned after any communication operations must not depend on those communication. Our model assumes goroutines are spawned independenly. ### MiGo types approach This approach generates MiGo types, a behavioural type introduced in [this work][popl17], to check for safety and liveness by a restriction called *fencing* on channel usage (See [paper][popl17]). The checker for MiGo types is available at [nickng/gong](https://github.com/nickng/gong), follow the instructions to build the tool: $ git clone ssh://github.com/nickng/gong.git $ cd gong; ghc Gong.hs To run MiGo types generation on `example/local-deadlock/main.go`: $ dingo-hunter migo example/local-deadlock/main.go --no-logging --output deadlock.migo $ /path/to/Gong -A deadlock.migo #### Limitations * Channels as return values are not supported right now * Channel recv,ok test not possible to represent in MiGo (requires inspecting value but abstracted by types) ## Research publications * [Static Deadlock Detection for Concurrent Go by Global Session Graph Synthesis][cc16], Nicholas Ng and Nobuko Yoshida, Int'l Conference on Compiler Construction (CC 2016), ACM * [Fencing off Go: Liveness and Safety for Channel-based Programming][popl17], Julien Lange, Nicholas Ng, Bernardo Toninho and Nobuko Yoshida, ACM SIGPLAN Symposium on Principles of Programming Languages (POPL 2017), ACM ## Notes This is a research prototype, and may not work for all Go source code. Please file an issue for problems that look like a bug. ## License dingo-hunter is licensed under the [Apache License](http://www.apache.org/licenses/LICENSE-2.0) [cc16]: http://dl.acm.org/citation.cfm?doid=2892208.2892232 "Static Deadlock Detection for Concurrent Go by Global Graph Synthesis" [popl17]: http://dl.acm.org/citation.cfm?doiid=3009837.3009847 "Fencing off Go: Liveness and Safety for Channel-based Programming" ================================================ FILE: cfsmextract/cfsmextract.go ================================================ package cfsmextract // This file contains only the functions needed to start the analysis // - Handle command line flags // - Set up session variables import ( "fmt" "go/types" "log" "os" "time" "github.com/nickng/dingo-hunter/cfsmextract/sesstype" "github.com/nickng/dingo-hunter/cfsmextract/utils" "github.com/nickng/dingo-hunter/ssabuilder" "golang.org/x/tools/go/ssa" ) type CFSMExtract struct { SSA *ssabuilder.SSAInfo Time time.Duration Done chan struct{} Error chan error session *sesstype.Session goQueue []*frame prefix string outdir string } func New(ssainfo *ssabuilder.SSAInfo, prefix, outdir string) *CFSMExtract { return &CFSMExtract{ SSA: ssainfo, Done: make(chan struct{}), Error: make(chan error), session: sesstype.CreateSession(), goQueue: []*frame{}, prefix: prefix, outdir: outdir, } } // Run function analyses main.main() then all the goroutines collected, and // finally output the analysis results. func (extract *CFSMExtract) Run() { startTime := time.Now() mainPkg := ssabuilder.MainPkg(extract.SSA.Prog) if mainPkg == nil { fmt.Fprintf(os.Stderr, "Error: 'main' package not found\n") os.Exit(1) } init := mainPkg.Func("init") main := mainPkg.Func("main") fr := makeToplevelFrame(extract) for _, pkg := range extract.SSA.Prog.AllPackages() { for _, memb := range pkg.Members { switch val := memb.(type) { case *ssa.Global: switch derefAll(val.Type()).(type) { case *types.Array: vd := utils.NewDef(val) fr.env.globals[val] = vd fr.env.arrays[vd] = make(Elems) case *types.Struct: vd := utils.NewDef(val) fr.env.globals[val] = vd fr.env.structs[vd] = make(Fields) case *types.Chan: var c *types.Chan vd := utils.NewDef(utils.EmptyValue{T: c}) fr.env.globals[val] = vd default: fr.env.globals[val] = utils.NewDef(val) } } } } fmt.Fprintf(os.Stderr, "++ call.toplevel %s()\n", orange("init")) visitFunc(init, fr) if main == nil { fmt.Fprintf(os.Stderr, "Error: 'main()' function not found in 'main' package\n") os.Exit(1) } fmt.Fprintf(os.Stderr, "++ call.toplevel %s()\n", orange("main")) visitFunc(main, fr) fr.env.session.Types[fr.gortn.role] = fr.gortn.root var goFrm *frame for len(extract.goQueue) > 0 { goFrm, extract.goQueue = extract.goQueue[0], extract.goQueue[1:] fmt.Fprintf(os.Stderr, "\n%s\nLOCATION: %s%s\n", goFrm.fn.Name(), goFrm.gortn.role.Name(), loc(goFrm, goFrm.fn.Pos())) visitFunc(goFrm.fn, goFrm) goFrm.env.session.Types[goFrm.gortn.role] = goFrm.gortn.root } extract.Time = time.Since(startTime) extract.Done <- struct{}{} } // Session returns the session after extraction. func (extract *CFSMExtract) Session() *sesstype.Session { return extract.session } func (extract *CFSMExtract) WriteOutput() { fmt.Printf(" ----- Results ----- \n%s\n", extract.session.String()) sesstype.PrintNodeSummary(extract.session) dotFile, err := os.OpenFile(fmt.Sprintf("%s.dot", extract.prefix), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { panic(err) } defer dotFile.Close() dot := sesstype.NewGraphvizDot(extract.session) if _, err = dot.WriteTo(dotFile); err != nil { panic(err) } os.MkdirAll(extract.outdir, 0750) cfsmPath := fmt.Sprintf("%s/%s_cfsms", extract.outdir, extract.prefix) cfsmFile, err := os.OpenFile(cfsmPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { panic(err) } defer cfsmFile.Close() cfsms := sesstype.NewCFSMs(extract.session) if _, err := cfsms.WriteTo(cfsmFile); err != nil { log.Fatalf("Cannot write CFSMs to file: %v", err) } fmt.Fprintf(os.Stderr, "CFSMs written to %s\n", cfsmPath) cfsms.PrintSummary() } ================================================ FILE: cfsmextract/func.go ================================================ package cfsmextract import ( "fmt" "go/types" "os" "github.com/nickng/dingo-hunter/cfsmextract/sesstype" "github.com/nickng/dingo-hunter/cfsmextract/utils" "golang.org/x/tools/go/ssa" ) // VarKind specifies the type of utils.VarDef used in frame.local[] type VarKind int // VarKind definitions const ( Nothing VarKind = iota // Not in frame.local[] Array // Array in heap Chan // Channel Struct // Struct in heap LocalArray // Array in frame LocalStruct // Struct in frame Untracked // Not a tracked VarKind but in frame.local[] ) // Captures are lists of VarDefs for closure captures type Captures []*utils.Definition // Tuples are lists of VarDefs from multiple return values type Tuples []*utils.Definition // Elems are maps from array indices (variable) to VarDefs type Elems map[ssa.Value]*utils.Definition // Fields are maps from struct fields (integer) to VarDefs type Fields map[int]*utils.Definition // Frame holds variables in current function scope type frame struct { fn *ssa.Function // Function ptr of callee locals map[ssa.Value]*utils.Definition // Holds definitions of local registers arrays map[*utils.Definition]Elems // Array elements (Alloc local) structs map[*utils.Definition]Fields // Struct fields (Alloc local) tuples map[ssa.Value]Tuples // Multiple return values as tuple phi map[ssa.Value][]ssa.Value // Phis recvok map[ssa.Value]*sesstype.Chan // Channel used in recvok retvals Tuples // Return values to pass back to parent defers []*ssa.Defer // Deferred calls caller *frame // Ptr to caller's frame, nil if main/ext env *environ // Environment gortn *goroutine // Current goroutine } // Environment: Variables/info available globally for all goroutines type environ struct { session *sesstype.Session extract *CFSMExtract globals map[ssa.Value]*utils.Definition // Globals arrays map[*utils.Definition]Elems // Array elements structs map[*utils.Definition]Fields // Struct fields chans map[*utils.Definition]*sesstype.Chan // Channels extern map[ssa.Value]types.Type // Values that originates externally, we are only sure of its type closures map[ssa.Value]Captures // Closure captures selNode map[ssa.Value]struct { // Parent nodes of select parent *sesstype.Node blocking bool } selIdx map[ssa.Value]ssa.Value // Mapping from select index to select SSA Value selTest map[ssa.Value]struct { // Records test for select-branch index idx int // The index of the branch tpl ssa.Value // The SelectState tuple which the branch originates from } recvTest map[ssa.Value]*sesstype.Chan // Receive test ifparent *sesstype.NodeStack } func (env *environ) GetSessionChan(vd *utils.Definition) *sesstype.Chan { if ch, ok := env.session.Chans[vd]; ok { return &ch } panic(fmt.Sprintf("Channel %s undefined in session", vd.String())) } func makeToplevelFrame(extract *CFSMExtract) *frame { callee := &frame{ fn: nil, locals: make(map[ssa.Value]*utils.Definition), arrays: make(map[*utils.Definition]Elems), structs: make(map[*utils.Definition]Fields), tuples: make(map[ssa.Value]Tuples), phi: make(map[ssa.Value][]ssa.Value), recvok: make(map[ssa.Value]*sesstype.Chan), retvals: make(Tuples, 0), defers: make([]*ssa.Defer, 0), caller: nil, env: &environ{ session: extract.session, extract: extract, globals: make(map[ssa.Value]*utils.Definition), arrays: make(map[*utils.Definition]Elems), structs: make(map[*utils.Definition]Fields), chans: make(map[*utils.Definition]*sesstype.Chan), extern: make(map[ssa.Value]types.Type), closures: make(map[ssa.Value]Captures), selNode: make(map[ssa.Value]struct { parent *sesstype.Node blocking bool }), selIdx: make(map[ssa.Value]ssa.Value), selTest: make(map[ssa.Value]struct { idx int tpl ssa.Value }), recvTest: make(map[ssa.Value]*sesstype.Chan), ifparent: sesstype.NewNodeStack(), }, gortn: &goroutine{ role: extract.session.GetRole("main"), root: sesstype.NewLabelNode("main"), leaf: nil, visited: make(map[*ssa.BasicBlock]sesstype.Node), }, } callee.gortn.leaf = &callee.gortn.root return callee } func (caller *frame) callBuiltin(common *ssa.CallCommon) { builtin := common.Value.(*ssa.Builtin) if builtin.Name() == "close" { if len(common.Args) == 1 { if ch, ok := caller.env.chans[caller.locals[common.Args[0]]]; ok { fmt.Fprintf(os.Stderr, "++ call builtin %s(%s channel %s)\n", orange(builtin.Name()), green(common.Args[0].Name()), ch.Name()) visitClose(*ch, caller) } else { panic("Builtin close() called with non-channel\n") } } } else if builtin.Name() == "copy" { dst := common.Args[0] src := common.Args[1] fmt.Fprintf(os.Stderr, "++ call builtin %s(%s <- %s)\n", orange("copy"), dst.Name(), src.Name()) caller.locals[dst] = caller.locals[src] return } else { fmt.Fprintf(os.Stderr, "++ call builtin %s(", builtin.Name()) for _, arg := range common.Args { fmt.Fprintf(os.Stderr, "%s", arg.Name()) } fmt.Fprintf(os.Stderr, ") # TODO (handle builtin)\n") } } func (caller *frame) call(c *ssa.Call) { caller.callCommon(c, c.Common()) } func (caller *frame) callCommon(call *ssa.Call, common *ssa.CallCommon) { switch fn := common.Value.(type) { case *ssa.Builtin: caller.callBuiltin(common) case *ssa.MakeClosure: // TODO(nickng) Handle calling closure fmt.Fprintf(os.Stderr, " # TODO (handle closure) %s\n", fn.String()) case *ssa.Function: if common.StaticCallee() == nil { panic("Call with nil CallCommon!") } callee := &frame{ fn: common.StaticCallee(), locals: make(map[ssa.Value]*utils.Definition), arrays: make(map[*utils.Definition]Elems), structs: make(map[*utils.Definition]Fields), tuples: make(map[ssa.Value]Tuples), phi: make(map[ssa.Value][]ssa.Value), recvok: make(map[ssa.Value]*sesstype.Chan), retvals: make(Tuples, common.Signature().Results().Len()), defers: make([]*ssa.Defer, 0), caller: caller, env: caller.env, // Use the same env as caller gortn: caller.gortn, // Use the same role as caller } fmt.Fprintf(os.Stderr, "++ call %s(", orange(common.StaticCallee().String())) callee.translate(common) fmt.Fprintf(os.Stderr, ")\n") if callee.isRecursive() { fmt.Fprintf(os.Stderr, "-- Recursive %s()\n", orange(common.StaticCallee().String())) callee.printCallStack() } else { if hasCode := visitFunc(callee.fn, callee); hasCode { caller.handleRetvals(call.Value(), callee) } else { caller.handleExtRetvals(call.Value(), callee) } fmt.Fprintf(os.Stderr, "-- return from %s (%d retvals)\n", orange(common.StaticCallee().String()), len(callee.retvals)) } default: if !common.IsInvoke() { fmt.Fprintf(os.Stderr, "Unknown call type %v\n", common) return } switch vd, kind := caller.get(common.Value); kind { case Struct, LocalStruct: fmt.Fprintf(os.Stderr, "++ invoke %s.%s, type=%s\n", reg(common.Value), common.Method.String(), vd.Var.Type().String()) // If dealing with interfaces, check that the method is invokable if iface, ok := common.Value.Type().Underlying().(*types.Interface); ok { if meth, _ := types.MissingMethod(vd.Var.Type(), iface, true); meth != nil { fmt.Fprintf(os.Stderr, " ^ interface not fully implemented\n") } else { fn := findMethod(common.Value.Parent().Prog, common.Method, vd.Var.Type()) if fn != nil { fmt.Fprintf(os.Stderr, " ^ found function %s\n", fn.String()) callee := &frame{ fn: fn, locals: make(map[ssa.Value]*utils.Definition), arrays: make(map[*utils.Definition]Elems), structs: make(map[*utils.Definition]Fields), tuples: make(map[ssa.Value]Tuples), phi: make(map[ssa.Value][]ssa.Value), recvok: make(map[ssa.Value]*sesstype.Chan), retvals: make(Tuples, common.Signature().Results().Len()), defers: make([]*ssa.Defer, 0), caller: caller, env: caller.env, // Use the same env as caller gortn: caller.gortn, // Use the same role as caller } common.Args = append([]ssa.Value{common.Value}, common.Args...) fmt.Fprintf(os.Stderr, "++ call %s(", orange(fn.String())) callee.translate(common) fmt.Fprintf(os.Stderr, ")\n") if callee.isRecursive() { fmt.Fprintf(os.Stderr, "-- Recursive %s()\n", orange(fn.String())) callee.printCallStack() } else { if hasCode := visitFunc(callee.fn, callee); hasCode { caller.handleRetvals(call.Value(), callee) } else { caller.handleExtRetvals(call.Value(), callee) } fmt.Fprintf(os.Stderr, "-- return from %s (%d retvals)\n", orange(fn.String()), len(callee.retvals)) } } else { panic(fmt.Sprintf("Cannot call function: %s.%s is abstract (program not well-formed)", common.Value, common.Method.String())) } } } else { fmt.Fprintf(os.Stderr, " ^ method %s.%s does not exist\n", reg(common.Value), common.Method.String()) } default: fmt.Fprintf(os.Stderr, "++ invoke %s.%s\n", reg(common.Value), common.Method.String()) } } } func findMethod(prog *ssa.Program, meth *types.Func, typ types.Type) *ssa.Function { if meth != nil { fmt.Fprintf(os.Stderr, " ^ finding method for type: %s pkg: %s name: %s\n", typ.String(), meth.Pkg().Name(), meth.Name()) } return prog.LookupMethod(typ, meth.Pkg(), meth.Name()) } func (caller *frame) callGo(g *ssa.Go) { common := g.Common() goname := fmt.Sprintf("%s_%d", common.Value.Name(), int(g.Pos())) gorole := caller.env.session.GetRole(goname) callee := &frame{ fn: common.StaticCallee(), locals: make(map[ssa.Value]*utils.Definition), arrays: make(map[*utils.Definition]Elems), structs: make(map[*utils.Definition]Fields), tuples: make(map[ssa.Value]Tuples), phi: make(map[ssa.Value][]ssa.Value), recvok: make(map[ssa.Value]*sesstype.Chan), retvals: make(Tuples, common.Signature().Results().Len()), defers: make([]*ssa.Defer, 0), caller: caller, env: caller.env, // Use the same env as caller gortn: &goroutine{ role: gorole, root: sesstype.NewLabelNode(goname), leaf: nil, visited: make(map[*ssa.BasicBlock]sesstype.Node), }, } callee.gortn.leaf = &callee.gortn.root fmt.Fprintf(os.Stderr, "@@ queue go %s(", common.StaticCallee().String()) callee.translate(common) fmt.Fprintf(os.Stderr, ")\n") // TODO(nickng) Does not stop at recursive call. caller.env.extract.goQueue = append(caller.env.extract.goQueue, callee) } func (callee *frame) translate(common *ssa.CallCommon) { for i, param := range callee.fn.Params { argParent := common.Args[i] if param != argParent { if vd, ok := callee.caller.locals[argParent]; ok { callee.locals[param] = vd } } if i > 0 { fmt.Fprintf(os.Stderr, ", ") } fmt.Fprintf(os.Stderr, "%s:caller[%s] = %s", orange(param.Name()), reg(common.Args[i]), callee.locals[param].String()) myVD := callee.locals[param] // VD of parameter (which are in callee.locals) // if argument is a channel if ch, ok := callee.env.chans[myVD]; ok { fmt.Fprintf(os.Stderr, " channel %s", (*ch).Name()) } else if _, ok := callee.env.structs[myVD]; ok { fmt.Fprintf(os.Stderr, " struct") } else if _, ok := callee.env.arrays[myVD]; ok { fmt.Fprintf(os.Stderr, " array") } else if fields, ok := callee.caller.structs[myVD]; ok { // If param is local struct in caller, make local copy fmt.Fprintf(os.Stderr, " lstruct") callee.structs[myVD] = fields } else if elems, ok := callee.caller.arrays[myVD]; ok { // If param is local array in caller, make local copy fmt.Fprintf(os.Stderr, " larray") callee.arrays[myVD] = elems } } // Closure capture (copy from env.closures assigned in MakeClosure). if captures, isClosure := callee.env.closures[common.Value]; isClosure { for idx, fv := range callee.fn.FreeVars { callee.locals[fv] = captures[idx] fmt.Fprintf(os.Stderr, ", capture %s = %s", fv.Name(), captures[idx].String()) } } } // handleRetvals looks up and stores return value from function calls. // Nothing will be done if there are no return values from the function. func (caller *frame) handleRetvals(returned ssa.Value, callee *frame) { if len(callee.retvals) > 0 { if len(callee.retvals) == 1 { // Single return value (callee.retvals[0]) caller.locals[returned] = callee.retvals[0] } else { // Multiple return values (callee.retvals tuple) caller.tuples[returned] = callee.retvals } } } func (callee *frame) get(v ssa.Value) (*utils.Definition, VarKind) { if vd, ok := callee.locals[v]; ok { if _, ok := callee.env.arrays[vd]; ok { return vd, Array } if _, ok := callee.arrays[vd]; ok { return vd, LocalArray } if _, ok := callee.env.chans[vd]; ok { return vd, Chan } if _, ok := callee.env.structs[vd]; ok { return vd, Struct } if _, ok := callee.structs[vd]; ok { return vd, LocalStruct } return vd, Untracked } else if vs, ok := callee.phi[v]; ok { for i := len(vs) - 1; i >= 0; i-- { if chVd, defined := callee.locals[vs[i]]; defined { return chVd, Chan } } } return nil, Nothing } // handleExtRetvals looks up and stores return value from (ext) function calls. // Ext functions have no code (no body to analyse) and unlike normal values, // the return values/tuples are stored until they are referenced. func (caller *frame) handleExtRetvals(returned ssa.Value, callee *frame) { // Since there are no code for the function, we use the function // signature to see if any of these are channels. // XXX We don't know where these come from so we put them in extern. resultsLen := callee.fn.Signature.Results().Len() if resultsLen > 0 { caller.env.extern[returned] = callee.fn.Signature.Results() if resultsLen == 1 { fmt.Fprintf(os.Stderr, "-- Return from %s (builtin/ext) with a single value\n", callee.fn.String()) if _, ok := callee.fn.Signature.Results().At(0).Type().(*types.Chan); ok { vardef := utils.NewDef(returned) ch := caller.env.session.MakeExtChan(vardef, caller.gortn.role) caller.env.chans[vardef] = &ch fmt.Fprintf(os.Stderr, "-- Return value from %s (builtin/ext) is a channel %s (ext)\n", callee.fn.String(), (*caller.env.chans[vardef]).Name()) } } else { fmt.Fprintf(os.Stderr, "-- Return from %s (builtin/ext) with %d-tuple\n", callee.fn.String(), resultsLen) } } } func (callee *frame) isRecursive() bool { var tracebackFns []*ssa.Function foundFr := callee for fr := callee.caller; fr != nil; fr = fr.caller { tracebackFns = append(tracebackFns, fr.fn) if fr.fn == callee.fn { foundFr = fr break } } // If same function is not found, not recursive if foundFr == callee { return false } // Otherwise try to trace back with foundFr and is recursive if all matches for _, fn := range tracebackFns { if foundFr == nil || foundFr.fn != fn { return false } foundFr = foundFr.caller } return true } func (callee *frame) printCallStack() { curFr := callee for curFr != nil && curFr.fn != nil { fmt.Fprintf(os.Stderr, "Called by: %s()\n", curFr.fn.String()) curFr = curFr.caller } } func (callee *frame) updateDefs(vdOld, vdNew *utils.Definition) { for def, array := range callee.arrays { for k, v := range array { if v == vdOld { callee.arrays[def][k] = vdNew } } } for def, array := range callee.env.arrays { for k, v := range array { if v == vdOld { callee.env.arrays[def][k] = vdNew } } } for def, struc := range callee.structs { for i, field := range struc { if field == vdOld { callee.structs[def][i] = vdNew } } } for def, struc := range callee.env.structs { for i, field := range struc { if field == vdOld { callee.env.structs[def][i] = vdNew } } } } ================================================ FILE: cfsmextract/gortn.go ================================================ package cfsmextract import ( "github.com/nickng/dingo-hunter/cfsmextract/sesstype" "golang.org/x/tools/go/ssa" ) type goroutine struct { role sesstype.Role root sesstype.Node leaf *sesstype.Node visited map[*ssa.BasicBlock]sesstype.Node } // Append a session type node to current goroutine. func (gortn *goroutine) AddNode(node sesstype.Node) { if gortn.leaf == nil { panic("AddNode: leaf cannot be nil") } newLeaf := (*gortn.leaf).Append(node) gortn.leaf = &newLeaf } ================================================ FILE: cfsmextract/sesstype/cfsm.go ================================================ package sesstype import ( "fmt" "io" "log" "github.com/nickng/cfsm" ) // STOP is the 'close' message. const STOP = "STOP" // CFSMs captures a CFSM system syserated from a Session. type CFSMs struct { Sys *cfsm.System Chans map[Role]*cfsm.CFSM Roles map[Role]*cfsm.CFSM States map[*cfsm.CFSM]map[string]*cfsm.State } func NewCFSMs(s *Session) *CFSMs { sys := &CFSMs{ Sys: cfsm.NewSystem(), Chans: make(map[Role]*cfsm.CFSM), Roles: make(map[Role]*cfsm.CFSM), States: make(map[*cfsm.CFSM]map[string]*cfsm.State), } for _, c := range s.Chans { m := sys.Sys.NewMachine() m.Comment = c.Name() sys.Chans[c] = m defer sys.chanToMachine(c, c.Type().String(), m) } for role, root := range s.Types { m := sys.Sys.NewMachine() m.Comment = role.Name() sys.Roles[role] = m sys.States[m] = make(map[string]*cfsm.State) sys.rootToMachine(role, root, m) if m.IsEmpty() { log.Println("Machine", m.ID, "is empty") sys.Sys.RemoveMachine(m.ID) delete(sys.Roles, role) delete(sys.States, m) } } return sys } // WriteTo implementers io.WriterTo interface. func (sys *CFSMs) WriteTo(w io.Writer) (int64, error) { n, err := w.Write([]byte(sys.Sys.String())) return int64(n), err } // PrintSummary shows the statistics of the CFSM syseration. func (sys *CFSMs) PrintSummary() { fmt.Printf("Total of %d CFSMs (%d are channels)\n", len(sys.Roles)+len(sys.Chans), len(sys.Chans)) for r, m := range sys.Chans { fmt.Printf("\t%d\t= %s (channel)\n", m.ID, r.Name()) } for r, m := range sys.Roles { fmt.Printf("\t%d\t= %s\n", m.ID, r.Name()) } } func (sys *CFSMs) rootToMachine(role Role, root Node, m *cfsm.CFSM) { q0 := m.NewState() sys.nodeToMachine(role, root, q0, m) m.Start = q0 } func (sys *CFSMs) nodeToMachine(role Role, node Node, q0 *cfsm.State, m *cfsm.CFSM) { switch node := node.(type) { case *SendNode: to, ok := sys.Chans[node.To()] if !ok { log.Fatal("Cannot Send to unknown channel", node.To().Name()) } tr := cfsm.NewSend(to, node.To().Type().String()) var qSent *cfsm.State if sys.isSelfLoop(m, q0, node) { qSent = q0 } else { qSent = m.NewState() for _, c := range node.Children() { sys.nodeToMachine(role, c, qSent, m) } } tr.SetNext(qSent) q0.AddTransition(tr) case *RecvNode: from, ok := sys.Chans[node.From()] if !ok { log.Fatal("Cannot Recv from unknown channel", node.From().Name()) } msg := node.From().Type().String() if node.Stop() { msg = STOP } tr := cfsm.NewRecv(from, msg) var qRcvd *cfsm.State if sys.isSelfLoop(m, q0, node) { qRcvd = q0 } else { qRcvd = m.NewState() for _, c := range node.Children() { sys.nodeToMachine(role, c, qRcvd, m) } } tr.SetNext(qRcvd) q0.AddTransition(tr) case *EndNode: ch, ok := sys.Chans[node.Chan()] if !ok { log.Fatal("Cannot Close unknown channel", node.Chan().Name()) } tr := cfsm.NewSend(ch, STOP) qEnd := m.NewState() for _, c := range node.Children() { sys.nodeToMachine(role, c, qEnd, m) } tr.SetNext(qEnd) q0.AddTransition(tr) case *NewChanNode, *EmptyBodyNode: // Skip for _, c := range node.Children() { sys.nodeToMachine(role, c, q0, m) } case *LabelNode: sys.States[m][node.Name()] = q0 for _, c := range node.Children() { sys.nodeToMachine(role, c, q0, m) } case *GotoNode: qTarget := sys.States[m][node.Name()] for _, c := range node.Children() { sys.nodeToMachine(role, c, qTarget, m) } default: log.Fatalf("Unhandled node type %T", node) } } func (sys *CFSMs) chanToMachine(ch Role, T string, m *cfsm.CFSM) { q0 := m.NewState() qEnd := m.NewState() for _, machine := range sys.Roles { q1 := m.NewState() // q0 -- Recv --> q1 tr0 := cfsm.NewRecv(machine, T) tr0.SetNext(q1) q0.AddTransition(tr0) // q1 -- Send --> q0 for _, machine2 := range sys.Roles { if machine.ID != machine2.ID { tr1 := cfsm.NewSend(machine2, T) tr1.SetNext(q0) q1.AddTransition(tr1) } } // q0 -- STOP --> qEnd (same qEnd) tr2 := cfsm.NewRecv(machine, STOP) tr2.SetNext(qEnd) qEnd.AddTransition(tr2) // qEnd -- STOP --> qEnd for _, machine2 := range sys.Roles { if machine.ID != machine2.ID { tr3 := cfsm.NewSend(machine2, STOP) tr3.SetNext(qEnd) qEnd.AddTransition(tr3) } } } m.Start = q0 } // isSelfLoop returns true if the action of node is a self-loop // i.e. the state before and after the transition is the same. func (sys *CFSMs) isSelfLoop(m *cfsm.CFSM, q0 *cfsm.State, node Node) bool { if len(node.Children()) == 1 { if gotoNode, ok := node.Child(0).(*GotoNode); ok { if loopback, ok := sys.States[m][gotoNode.Name()]; ok { return loopback == q0 } } } return false } ================================================ FILE: cfsmextract/sesstype/dot.go ================================================ package sesstype import ( "fmt" "io" "log" "github.com/awalterschulze/gographviz" ) // GraphvizDot reprents a new graphviz dot graph. type GraphvizDot struct { Graph *gographviz.Escape Count int LabelNodes map[string]string } // NewGraphvizDot creates a new graphviz dot graph from a session. func NewGraphvizDot(s *Session) *GraphvizDot { dot := &GraphvizDot{ Graph: gographviz.NewEscape(), Count: 0, LabelNodes: make(map[string]string), } dot.Graph.SetDir(true) dot.Graph.SetName("G") for role, root := range s.Types { sg := gographviz.NewSubGraph("\"cluster_" + role.Name() + "\"") if root != nil { dot.visitNode(root, sg, nil) } dot.Graph.AddSubGraph(dot.Graph.Name, sg.Name, nil) } return dot } // WriteTo implements io.WriterTo interface. func (dot *GraphvizDot) WriteTo(w io.Writer) (int64, error) { n, err := w.Write([]byte(dot.Graph.String())) return int64(n), err } func (dot *GraphvizDot) nodeToDotNode(node Node) *gographviz.Node { switch node := node.(type) { case *LabelNode: defer func() { dot.Count++ }() dot.LabelNodes[node.Name()] = fmt.Sprintf("label%d", dot.Count) attrs, err := gographviz.NewAttrs(map[string]string{ "label": fmt.Sprintf("\"%s\"", node.String()), "shape": "plaintext,", }) if err != nil { log.Fatal(err) } dotNode := gographviz.Node{ Name: dot.LabelNodes[node.Name()], Attrs: attrs, } return &dotNode case *NewChanNode: defer func() { dot.Count++ }() attrs, err := gographviz.NewAttrs(map[string]string{ "label": fmt.Sprintf("Channel %s Type:%s", node.Chan().Name(), node.Chan().Type()), "shape": "rect", "color": "red", }) if err != nil { log.Fatal(err) } return &gographviz.Node{ Name: fmt.Sprintf("%s%d", node.Kind(), dot.Count), Attrs: attrs, } case *SendNode: defer func() { dot.Count++ }() style := "solid" desc := "" if node.IsNondet() { style = "dashed" desc = " nondet" } attrs, err := gographviz.NewAttrs(map[string]string{ "label": fmt.Sprintf("Send %s%s", node.To().Name(), desc), "shape": "rect", "style": style, }) if err != nil { log.Fatal(err) } return &gographviz.Node{ Name: fmt.Sprintf("%s%d", node.Kind(), dot.Count), Attrs: attrs, } case *RecvNode: defer func() { dot.Count++ }() style := "solid" desc := "" if node.IsNondet() { style = "dashed" desc = " nondet" } attrs, err := gographviz.NewAttrs(map[string]string{ "label": fmt.Sprintf("Recv %s%s", node.From().Name(), desc), "shape": "rect", "style": style, }) if err != nil { log.Fatal(err) } return &gographviz.Node{ Name: fmt.Sprintf("%s%d", node.Kind(), dot.Count), Attrs: attrs, } case *GotoNode: return nil // No new node to create default: defer func() { dot.Count++ }() attrs, err := gographviz.NewAttrs(map[string]string{ "label": fmt.Sprintf("\"%s\"", node.String()), "shape": "rect", }) if err != nil { log.Fatal(err) } dotNode := gographviz.Node{ Name: fmt.Sprintf("%s%d", node.Kind(), dot.Count), Attrs: attrs, } return &dotNode } } // visitNode Creates a dot Node and from it create a subgraph of children. // Returns head of the subgraph. func (dot *GraphvizDot) visitNode(node Node, subgraph *gographviz.SubGraph, parent *gographviz.Node) *gographviz.Node { dotNode := dot.nodeToDotNode(node) if dotNode == nil { // GotoNode gtn := node.(*GotoNode) dot.Graph.AddEdge(parent.Name, dot.LabelNodes[gtn.Name()], true, nil) for _, child := range node.Children() { dot.visitNode(child, subgraph, parent) } return parent // GotoNode's children are children of parent. So return parent. } attrs := make(map[string]string) for k, v := range dotNode.Attrs { attrs[string(k)] = v } dot.Graph.AddNode(subgraph.Name, dotNode.Name, attrs) if parent != nil { // Parent is not toplevel dot.Graph.AddEdge(parent.Name, dotNode.Name, true, nil) } for _, child := range node.Children() { dot.visitNode(child, subgraph, dotNode) } if dotNode == nil { panic("Cannot return nil dotNode") } return dotNode } ================================================ FILE: cfsmextract/sesstype/nodes.go ================================================ package sesstype import ( "fmt" ) func CountNodes(root Node) int { total := 1 for _, child := range root.Children() { total += CountNodes(child) } return total } func SessionCountNodes(session *Session) map[string]int { m := make(map[string]int) for r, t := range session.Types { m[r.Name()] = CountNodes(t) } return m } func PrintNodeSummary(session *Session) { counts := SessionCountNodes(session) fmt.Printf("Total of nodes per role (%d roles)\n", len(counts)) for role, n := range counts { fmt.Printf("\t%d\t: %s\n", n, role) } } ================================================ FILE: cfsmextract/sesstype/nodestack.go ================================================ package sesstype // import "github.com/nickng/dingo-hunter/cfsmextract/sesstype" // NodeStack is a stack for sesstype.Node type NodeStack struct { nodes []Node count int } // Push pushes a sesstype.Node to the stack. func (s *NodeStack) Push(node Node) { s.nodes = append(s.nodes[:s.count], node) s.count++ } // Pop removes a sesstype.Node from the stack. func (s *NodeStack) Pop() { if s.count <= 0 { panic("Cannot pop empty stack") } s.count-- } // Top returns the sesstype.Node at the top of the stack. func (s *NodeStack) Top() Node { if s.count <= 0 { return nil } return s.nodes[s.count-1] } // Size returns number of sesstype.Node on the stack. func (s *NodeStack) Size() int { return s.count } // String returns a String representing the stack. func (s *NodeStack) String() string { str := "[" for i := s.count - 1; i >= 0; i-- { str += s.nodes[i].String() if i != 0 { str += ", " } } str += "]" return str } // NewNodeStack returns a new NodeStack instance. func NewNodeStack() *NodeStack { return &NodeStack{} } ================================================ FILE: cfsmextract/sesstype/nodestack_test.go ================================================ package sesstype import ( "testing" ) func TestNewStack(t *testing.T) { ns := NewNodeStack() ns.Push(NewLabelNode("TEST")) ns.Push(NewLabelNode("TEST2")) ns.Push(NewLabelNode("TEST3")) l := ns.Top() if l.String() != "TEST3" { t.Fail() } ns.Pop() l2 := ns.Top() if l2.String() != "TEST2" { t.Fail() } ns.Pop() l3 := ns.Top() if l3.String() != "TEST" { t.Fail() } ns.Pop() } ================================================ FILE: cfsmextract/sesstype/op_string.go ================================================ // generated by stringer -type=op; DO NOT EDIT package sesstype import "fmt" const _op_name = "NoOpNewChanOpSendOpRecvOpEndOp" var _op_index = [...]uint8{0, 4, 13, 19, 25, 30} func (i op) String() string { if i < 0 || i >= op(len(_op_index)-1) { return fmt.Sprintf("op(%d)", i) } return _op_name[_op_index[i]:_op_index[i+1]] } ================================================ FILE: cfsmextract/sesstype/sesstype.go ================================================ // Package sesstype encapsulates representation of session types // As opposed to role-based session types, this representation is channel-based. // In particular, sending and receiving both keep track of the role and // channel involved. package sesstype // import "github.com/nickng/dingo-hunter/cfsmextract/sesstype" import ( "fmt" "go/types" "github.com/nickng/dingo-hunter/cfsmextract/utils" "golang.org/x/tools/go/ssa" ) //go:generate stringer -type=op type op int // Chan is a typed channel in a session. type Chan struct { def *utils.Definition role Role extern bool } // Return a name of channel. func (ch Chan) Name() string { fullname := fmt.Sprintf("%s", ch.def.String()) if ch.extern { return fullname + "*" } return fullname } // Return the payload type of channel. func (ch Chan) Type() types.Type { if c, ok := ch.def.Var.Type().(*types.Chan); ok { return c.Elem() } panic("Not channel " + ch.def.Var.String()) } func (ch Chan) Role() Role { return ch.role } func (ch Chan) Value() ssa.Value { return ch.def.Var } // Role in a session (main or goroutine). type Role interface { Name() string } type role struct { name string } func (r *role) Name() string { return r.name } // Different operations/actions available in session. const ( NoOp op = iota NewChanOp SendOp RecvOp EndOp ) // A Node in the session graph. type Node interface { Kind() op // For checking type without type switching Child(index int) Node // Gets child at index Append(child Node) Node // Returns new child for chaining Children() []Node // Returns whole slice String() string } // Session is a container of session graph nodes, also holds information about // channels and roles in the current session. type Session struct { Types map[Role]Node // Root Node for each Role Chans map[*utils.Definition]Chan // Actual channels are stored here Roles map[string]Role // Actual roles are stored here } // CreateSession initialises a new empty Session. func CreateSession() *Session { return &Session{ Types: make(map[Role]Node), Chans: make(map[*utils.Definition]Chan), Roles: make(map[string]Role), } } // GetRole returns or create (if empty) a new session role using given name. func (s *Session) GetRole(name string) Role { // Get or create role if _, found := s.Roles[name]; !found { s.Roles[name] = &role{name: name} } return s.Roles[name] } // MakeChan creates and stores a new session channel created. func (s *Session) MakeChan(v *utils.Definition, r Role) Chan { s.Chans[v] = Chan{ def: v, role: r, extern: false, } return s.Chans[v] } // MakeExtChan creates and stores a new channel and mark as externally created. func (s *Session) MakeExtChan(v *utils.Definition, r Role) Chan { s.Chans[v] = Chan{ def: v, role: r, extern: true, } return s.Chans[v] } // NewChanNode represents creation of new channel type NewChanNode struct { ch Chan children []Node } func (nc *NewChanNode) Kind() op { return NewChanOp } func (nc *NewChanNode) Chan() Chan { return nc.ch } func (nc *NewChanNode) Append(n Node) Node { nc.children = append(nc.children, n) return n } func (nc *NewChanNode) Child(i int) Node { return nc.children[i] } func (nc *NewChanNode) Children() []Node { return nc.children } func (nc *NewChanNode) String() string { return fmt.Sprintf("NewChan %s of type %s", nc.ch.Name(), nc.ch.Type().String()) } // SendNode represents a send. type SendNode struct { sndr Role // Sender dest Chan // Destination nondet bool // Is this non-deterministic? t types.Type // Datatype children []Node } func (s *SendNode) Kind() op { return SendOp } func (s *SendNode) Sender() Role { return s.sndr } func (s *SendNode) To() Chan { return s.dest } func (s *SendNode) IsNondet() bool { return s.nondet } func (s *SendNode) Append(n Node) Node { s.children = append(s.children, n) return n } func (s *SendNode) Child(i int) Node { return s.children[i] } func (s *SendNode) Children() []Node { return s.children } func (s *SendNode) String() string { var nd string if s.nondet { nd = "nondet " } return fmt.Sprintf("Send %s→ᶜʰ%s %s", s.sndr.Name(), s.dest.Name(), nd) } // RecvNode represents a receive. type RecvNode struct { orig Chan // Originates from rcvr Role // Received by nondet bool // Is this non-deterministic? t types.Type // Datatype stop bool // Stop message only? children []Node } func (r *RecvNode) Kind() op { return RecvOp } func (r *RecvNode) Receiver() Role { return r.rcvr } func (r *RecvNode) From() Chan { return r.orig } func (r *RecvNode) IsNondet() bool { return r.nondet } func (r *RecvNode) Stop() bool { return r.stop } func (r *RecvNode) Append(node Node) Node { r.children = append(r.children, node) return node } func (r *RecvNode) Child(index int) Node { return r.children[index] } func (r *RecvNode) Children() []Node { return r.children } func (r *RecvNode) String() string { var nd string if r.nondet { nd = "nondet " } if r.t == nil { return fmt.Sprintf("Recv END %s←ᶜʰ%s%s", r.rcvr.Name(), r.orig.Name(), nd) } return fmt.Sprintf("Recv %s←ᶜʰ%s%s", r.rcvr.Name(), r.orig.Name(), nd) } // LabelNode makes a placeholder for loop/jumping type LabelNode struct { name string children []Node } func (l *LabelNode) Kind() op { return NoOp } func (l *LabelNode) Name() string { return l.name } func (l *LabelNode) Append(n Node) Node { l.children = append(l.children, n) return n } func (l *LabelNode) Child(i int) Node { return l.children[i] } func (l *LabelNode) Children() []Node { return l.children } func (l *LabelNode) String() string { return fmt.Sprintf("%s", l.name) } // GotoNode represents a jump (edge to existing LabelNode) type GotoNode struct { name string children []Node } func (g *GotoNode) Kind() op { return NoOp } func (g *GotoNode) Name() string { return g.name } func (g *GotoNode) Append(n Node) Node { g.children = append(g.children, n) return n } func (g *GotoNode) Child(i int) Node { return g.children[i] } func (g *GotoNode) Children() []Node { return g.children } func (g *GotoNode) String() string { return fmt.Sprintf("Goto %s", g.name) } type EndNode struct { ch Chan children []Node } func (e *EndNode) Kind() op { return EndOp } func (e *EndNode) Chan() Chan { return e.ch } func (e *EndNode) Append(n Node) Node { e.children = append(e.children, n) return n } func (e *EndNode) Child(i int) Node { return e.children[i] } func (e *EndNode) Children() []Node { return e.children } func (e *EndNode) String() string { return fmt.Sprintf("End %s", e.ch.Name()) } type EmptyBodyNode struct { children []Node } func (e *EmptyBodyNode) Kind() op { return NoOp } func (e *EmptyBodyNode) Append(node Node) Node { e.children = append(e.children, node) return node } func (e *EmptyBodyNode) Child(i int) Node { return e.children[i] } func (e *EmptyBodyNode) Children() []Node { return e.children } func (e *EmptyBodyNode) String() string { return "(Empty)" } // NewNewChanNode makes a NewChanNode. func NewNewChanNode(ch Chan) Node { return &NewChanNode{ch: ch, children: []Node{}} } // NewSendNode makes a SendNode. func NewSendNode(sndr Role, dest Chan, typ types.Type) Node { return &SendNode{ sndr: sndr, dest: dest, nondet: false, t: typ, children: []Node{}, } } // NewSelectSendNode makes a SendNode in a select (non-deterministic). func NewSelectSendNode(sndr Role, dest Chan, typ types.Type) Node { return &SendNode{ sndr: sndr, dest: dest, nondet: true, t: typ, children: []Node{}, } } // NewRecvNode makes a RecvNode. func NewRecvNode(orig Chan, rcvr Role, typ types.Type) Node { return &RecvNode{ orig: orig, rcvr: rcvr, nondet: false, t: typ, stop: false, children: []Node{}, } } // NewRecvStopNode makes a RecvNode (for STOP messages). func NewRecvStopNode(orig Chan, rcvr Role, typ types.Type) Node { return &RecvNode{ orig: orig, rcvr: rcvr, nondet: false, t: typ, stop: true, children: []Node{}, } } // NewSelectRecvNode makes a RecvNode in a select (non-deterministic). func NewSelectRecvNode(orig Chan, rcvr Role, typ types.Type) Node { return &RecvNode{ orig: orig, rcvr: rcvr, nondet: true, t: typ, children: []Node{}, } } // NewLabelNode makes a LabelNode. func NewLabelNode(name string) Node { return &LabelNode{ name: name, children: []Node{}, } } // NewGotoNode makes a GotoNode. func NewGotoNode(name string) Node { return &GotoNode{ name: name, children: []Node{}, } } // NewEndNode makse an EndNode. func NewEndNode(ch Chan) Node { return &EndNode{ ch: ch, } } // String displays session details. func (s *Session) String() string { str := "# Channels\n" for _, ch := range s.Chans { str += fmt.Sprintf("%s ", ch.Name()) } str += "\n# Role\n" for _, r := range s.Roles { str += fmt.Sprintf("%s ", r.Name()) } str += "\n# Session\n" for role, session := range s.Types { str += fmt.Sprintf(" %s: %s", role.Name(), StringRecursive(session)) str += "\n" } return str } func StringRecursive(node Node) string { str := "" if node == nil { return str } str += node.String() + "; " switch len(node.Children()) { case 0: case 1: str += StringRecursive(node.Children()[0]) default: str += fmt.Sprintf("children: %d &{", len(node.Children())) for i, child := range node.Children() { if i > 0 { str += "," } str += StringRecursive(child) } str += "}" } return str } ================================================ FILE: cfsmextract/sesstype/sesstype_test.go ================================================ package sesstype import ( "go/token" "go/types" "testing" "github.com/nickng/cfsm" "github.com/nickng/dingo-hunter/cfsmextract/utils" "golang.org/x/tools/go/ssa" ) // Tests SendNode creation. func TestSendNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewSendNode(r, c, nil) if n.Kind() != SendOp { t.Errorf("Expecting node kind to be %s but got %s\n", SendOp, n.Kind()) } if n.(*SendNode).nondet { t.Errorf("Expecting Send to be deterministic by default\n") } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewSelectSendNode(r, c, nil) if n2.Kind() != SendOp { t.Errorf("Expecting node kind to be %s but got %s\n", SendOp, n2.Kind()) } if !n2.(*SendNode).nondet { t.Errorf("Expecting Select-Send to be non-deterministic by default\n") } if len(n2.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n2.Children())) } if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } } // Tests RecvNode creation. func TestRecvNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewRecvNode(c, r, nil) if n.Kind() != RecvOp { t.Errorf("Expecting node kind to be %s but got %s\n", RecvOp, n.Kind()) } if n.(*RecvNode).nondet { t.Errorf("Expecting Receive to be deterministic by default\n") } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewSelectRecvNode(c, r, nil) if n2.Kind() != RecvOp { t.Errorf("Expecting node kind to be %s but got %s\n", RecvOp, n2.Kind()) } if !n2.(*RecvNode).nondet { t.Errorf("Expecting Select-Recv to be non-deterministic by default\n") } if len(n2.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n2.Children())) } if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } } // Tests LabelNode and GotoNode creation. func TestLabelGotoNode(t *testing.T) { l := NewLabelNode("Name") if l.Kind() != NoOp { t.Errorf("Expecting Goto node kind to be %s but got %s\n", NoOp, l.Kind()) } if len(l.Children()) != 0 { t.Errorf("Expecting Label node to have 0 children but got %d\n", len(l.Children())) } g := NewGotoNode("Name") if g.Kind() != NoOp { t.Errorf("Expecting Goto node kind to be %s but got %s\n", NoOp, g.Kind()) } if len(g.Children()) != 0 { t.Errorf("Expecting Goto node to have 0 children but got %d\n", len(g.Children())) } if g != l.Append(g) { t.Error("Appended node is not same as expected\n") } if len(l.Children()) != 1 { t.Errorf("Expecting Label node to have 1 children but got %d\n", len(l.Children())) } } // Tests NewChanNode creation. func TestNewChanNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewNewChanNode(c) if n.Kind() != NewChanOp { t.Errorf("Expecting node kind to be %s but got %s\n", NewChanOp, n.Kind()) } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewNewChanNode(c) if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } } // Tests NewEndNode creation. func TestEndNode(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(utils.EmptyValue{T: nil}), r) n := NewEndNode(c) if n.Kind() != EndOp { t.Errorf("Expecting node kind to be %s but got %s\n", EndOp, n.Kind()) } if len(n.Children()) != 0 { t.Errorf("Expecting node to have 0 children but got %d\n", len(n.Children())) } n2 := NewEndNode(c) if n2 != n.Append(n2) { t.Errorf("Appended node is not same as expected\n") } if len(n.Children()) != 1 { t.Errorf("Expecting node to have 1 children but got %d\n", len(n.Children())) } } type mockChan struct{} func (mc mockChan) Name() string { return "MockChan" } func (mc mockChan) String() string { return "Mock Chan" } func (mc mockChan) Type() types.Type { return types.NewChan(types.SendRecv, types.NewStruct(nil, nil)) } func (mc mockChan) Parent() *ssa.Function { return nil } func (mc mockChan) Referrers() *[]ssa.Instruction { return nil } func (mc mockChan) Pos() token.Pos { return token.NoPos } func TestSelfLoop(t *testing.T) { s := CreateSession() r := s.GetRole("main") c := s.MakeChan(utils.NewDef(mockChan{}), r) n0 := NewLabelNode("BeforeReceive") n1 := NewRecvNode(c, r, types.NewStruct(nil, nil)) n2 := NewGotoNode("BeforeReceive") n0.Append(n1) n1.Append(n2) ms := NewCFSMs(s) m := ms.Sys.NewMachine() ms.States[m] = make(map[string]*cfsm.State) ms.rootToMachine(r, n0, m) if want, got := 1, len(m.States()); want != got { t.Errorf("expecting %d states for self-loop but got %d", want, got) } if want, got := 1, len(m.States()[0].Transitions()); want != got { t.Errorf("expecting %d transitions for self-loop but got %d", want, got) } if from, to := m.States()[0], m.States()[0].Transitions()[0].State(); from != to { t.Errorf("expecting self-loop but got %s", m.String()) } } ================================================ FILE: cfsmextract/utils/defs.go ================================================ package utils import ( "fmt" "golang.org/x/tools/go/ssa" ) var ( VarVers = make(map[ssa.Value]int) ) // Variable definitions type Definition struct { Var ssa.Value Ver int } // NewVarDef creates a new variable definition from an ssa.Value func NewDef(v ssa.Value) *Definition { if v == nil { panic("NewVarDef: Cannot create new VarDef with nil") } if ver, ok := VarVers[v]; ok { VarVers[v]++ return &Definition{ Var: v, Ver: ver + 1, } } VarVers[v] = 0 return &Definition{ Var: v, Ver: 0, } } func (vd *Definition) String() string { if vd == nil || vd.Var == nil { return "Undefined" } if vd.Var.Parent() != nil { return fmt.Sprintf("%s.%s@%d", vd.Var.Parent().String(), vd.Var.Name(), vd.Ver) } return fmt.Sprintf("???.%s@%d", vd.Var.Name(), vd.Ver) } ================================================ FILE: cfsmextract/utils/emptyvalue.go ================================================ package utils import ( "go/token" "go/types" "golang.org/x/tools/go/ssa" ) var ( _ ssa.Value = (*EmptyValue)(nil) // Make sure it implements ssa.Value. ) // EmptyValue is a ssa.Value placeholder for values we don't care. type EmptyValue struct { T types.Type } func (v EmptyValue) Name() string { return "(Nothingness)" } func (v EmptyValue) String() string { return "(Empty Value)" } func (v EmptyValue) Type() types.Type { return v.T } func (v EmptyValue) Parent() *ssa.Function { return nil } func (v EmptyValue) Referrers() *[]ssa.Instruction { return nil } func (v EmptyValue) Pos() token.Pos { return token.NoPos } ================================================ FILE: cfsmextract/utils.go ================================================ package cfsmextract // From golang.org/x/tools/go/ssa/interp/interp.go import ( "fmt" "go/token" "go/types" "golang.org/x/tools/go/ssa" ) func loc(fr *frame, pos token.Pos) string { if fr.fn == nil { return "(unknown)" } if pos == token.NoPos { return "(unknown)" } return fr.fn.Prog.Fset.Position(pos).String() } func red(s string) string { return fmt.Sprintf("\033[31m%s\033[0m", s) } func orange(s string) string { return fmt.Sprintf("\033[33m%s\033[0m", s) } func green(s string) string { return fmt.Sprintf("\033[32m%s\033[0m", s) } func cyan(s string) string { return fmt.Sprintf("\033[36m%s\033[0m", s) } func reg(reg ssa.Value) string { if reg == nil { return "???.nil" } if reg.Parent() != nil { return fmt.Sprintf("%s.\033[4m%s\033[0m", reg.Parent().String(), reg.Name()) } return fmt.Sprintf("???.\033[4m%s\033[0m", reg.Name()) } func deref(typ types.Type) types.Type { if p, ok := typ.Underlying().(*types.Pointer); ok { return p.Elem() } return typ } func derefAll(typ types.Type) types.Type { t := typ for { if p, ok := t.Underlying().(*types.Pointer); ok { t = p.Elem() } else { return t } } } ================================================ FILE: cfsmextract/visit.go ================================================ package cfsmextract import ( "fmt" "go/token" "go/types" "os" "github.com/nickng/dingo-hunter/cfsmextract/sesstype" "github.com/nickng/dingo-hunter/cfsmextract/utils" "golang.org/x/tools/go/ssa" ) func visitBlock(blk *ssa.BasicBlock, fr *frame) { if len(blk.Preds) > 1 { blkLabel := fmt.Sprintf("%s#%d", blk.Parent().String(), blk.Index) if _, found := fr.gortn.visited[blk]; found { fr.gortn.AddNode(sesstype.NewGotoNode(blkLabel)) return } // Make a label for other edges that enter this block label := sesstype.NewLabelNode(blkLabel) fr.gortn.AddNode(label) fr.gortn.visited[blk] = label // XXX visited is initialised by append if lblNode is head of tree } for _, inst := range blk.Instrs { visitInst(inst, fr) } } // visitFunc is called to traverse a function using given callee frame // Returns a boolean representing whether or not there are code in the func. func visitFunc(fn *ssa.Function, callee *frame) bool { if fn.Blocks == nil { //fmt.Fprintf(os.Stderr, " # Ignore builtin/external '"+fn.String()+"' with no Blocks\n") return false } visitBlock(fn.Blocks[0], callee) return true } func visitInst(inst ssa.Instruction, fr *frame) { switch inst := inst.(type) { case *ssa.MakeChan: visitMakeChan(inst, fr) case *ssa.Send: visitSend(inst, fr) case *ssa.UnOp: switch inst.Op { case token.ARROW: visitRecv(inst, fr) case token.MUL: visitDeref(inst, fr) default: fmt.Fprintf(os.Stderr, " # unhandled %s = %s\n", red(inst.Name()), red(inst.String())) } case *ssa.Call: visitCall(inst, fr) case *ssa.Extract: visitExtract(inst, fr) case *ssa.Go: fr.callGo(inst) case *ssa.Return: fr.retvals = visitReturn(inst, fr) case *ssa.Store: visitStore(inst, fr) case *ssa.Alloc: visitAlloc(inst, fr) case *ssa.MakeClosure: visitMakeClosure(inst, fr) case *ssa.Select: visitSelect(inst, fr) case *ssa.ChangeType: visitChangeType(inst, fr) case *ssa.ChangeInterface: visitChangeInterface(inst, fr) case *ssa.If: visitIf(inst, fr) case *ssa.Jump: visitJump(inst, fr) case *ssa.BinOp: visitBinOp(inst, fr) case *ssa.Slice: visitSlice(inst, fr) case *ssa.MakeSlice: visitMakeSlice(inst, fr) case *ssa.FieldAddr: visitFieldAddr(inst, fr) case *ssa.Field: visitField(inst, fr) case *ssa.IndexAddr: visitIndexAddr(inst, fr) case *ssa.Index: visitIndex(inst, fr) case *ssa.Defer: visitDefer(inst, fr) case *ssa.RunDefers: visitRunDefers(inst, fr) case *ssa.Phi: visitPhi(inst, fr) case *ssa.TypeAssert: visitTypeAssert(inst, fr) case *ssa.MakeInterface: visitMakeInterface(inst, fr) case *ssa.DebugRef: default: // Everything else not handled yet if v, ok := inst.(ssa.Value); ok { fmt.Fprintf(os.Stderr, " # unhandled %s = %s\n", red(v.Name()), red(v.String())) } else { fmt.Fprintf(os.Stderr, " # unhandled %s\n", red(inst.String())) } } } func visitExtract(e *ssa.Extract, fr *frame) { if recvCh, ok := fr.recvok[e.Tuple]; ok && e.Index == 1 { // 1 = ok (bool) fmt.Fprintf(os.Stderr, " EXTRACT for %s\n", recvCh.Name()) //fr.locals[e] = e fr.env.recvTest[e] = recvCh return } if tpl, ok := fr.tuples[e.Tuple]; ok { fmt.Fprintf(os.Stderr, " %s = extract %s[#%d] == %s\n", reg(e), e.Tuple.Name(), e.Index, tpl[e.Index].String()) fr.locals[e] = tpl[e.Index] } else { // Check if we are extracting select index if _, ok := fr.env.selNode[e.Tuple]; ok && e.Index == 0 { fmt.Fprintf(os.Stderr, " | %s = select %s index\n", e.Name(), e.Tuple.Name()) fr.env.selIdx[e] = e.Tuple return } // Check if value is an external tuple (return value) if extType, isExtern := fr.env.extern[e.Tuple]; isExtern { if extTpl, isTuple := extType.(*types.Tuple); isTuple { if extTpl.Len() < e.Index { panic(fmt.Sprintf("Extract: Cannot extract from tuple %s\n", e.Tuple.Name())) } // if extracted value is a chan create a new channel for it if _, ok := extTpl.At(e.Index).Type().(*types.Chan); ok { panic("Extract: Undefined channel") } } if e.Index < len(tpl) { fmt.Fprintf(os.Stderr, " extract %s[#%d] == %s\n", e.Tuple.Name(), e.Index, tpl[e.Index].String()) } else { fmt.Fprintf(os.Stderr, " extract %s[#%d/%d]\n", e.Tuple.Name(), e.Index, len(tpl)) } } else { fmt.Fprintf(os.Stderr, " # %s = %s of type %s\n", e.Name(), red(e.String()), e.Type().String()) switch derefAll(e.Type()).Underlying().(type) { case *types.Array: vd := utils.NewDef(e) fr.locals[e] = vd fr.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " ^ local array (used as definition)\n") case *types.Struct: vd := utils.NewDef(e) fr.locals[e] = vd fr.structs[vd] = make(Fields) fmt.Fprintf(os.Stderr, " ^ local struct (used as definition)\n") } } } } func visitMakeClosure(inst *ssa.MakeClosure, fr *frame) { fr.env.closures[inst] = make([]*utils.Definition, 0) for _, binding := range inst.Bindings { fr.env.closures[inst] = append(fr.env.closures[inst], fr.locals[binding]) } } // visitAlloc is for variable allocation (usually by 'new') // Everything allocated here are pointers func visitAlloc(inst *ssa.Alloc, fr *frame) { locn := loc(fr, inst.Pos()) allocType := inst.Type().(*types.Pointer).Elem() if allocType == nil { panic("Alloc: Cannot Alloc for non-pointer type") } var val ssa.Value = inst switch t := allocType.Underlying().(type) { case *types.Array: vd := utils.NewDef(val) fr.locals[val] = vd if inst.Heap { fr.env.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " %s = Alloc (array@heap) of type %s (%d elems) at %s\n", cyan(reg(inst)), inst.Type().String(), t.Len(), locn) } else { fr.arrays[vd] = make(Elems) fmt.Fprintf(os.Stderr, " %s = Alloc (array@local) of type %s (%d elems) at %s\n", cyan(reg(inst)), inst.Type().String(), t.Len(), locn) } case *types.Chan: // VD will be created in MakeChan so no need to allocate here. fmt.Fprintf(os.Stderr, " %s = Alloc (chan) of type %s at %s\n", cyan(reg(inst)), inst.Type().String(), locn) case *types.Struct: vd := utils.NewDef(val) fr.locals[val] = vd if inst.Heap { fr.env.structs[vd] = make(Fields, t.NumFields()) fmt.Fprintf(os.Stderr, " %s = Alloc (struct@heap) of type %s (%d fields) at %s\n", cyan(reg(inst)), inst.Type().String(), t.NumFields(), locn) } else { fr.structs[vd] = make(Fields, t.NumFields()) fmt.Fprintf(os.Stderr, " %s = Alloc (struct@local) of type %s (%d fields) at %s\n", cyan(reg(inst)), inst.Type().String(), t.NumFields(), locn) } default: fmt.Fprintf(os.Stderr, " # %s = "+red("Alloc %s")+" of type %s\n", inst.Name(), inst.String(), t.String()) } } func visitDeref(inst *ssa.UnOp, fr *frame) { ptr := inst.X val := inst if _, ok := ptr.(*ssa.Global); ok { fr.locals[ptr] = fr.env.globals[ptr] fmt.Fprintf(os.Stderr, " %s = *%s (global) of type %s\n", cyan(reg(val)), ptr.Name(), ptr.Type().String()) fmt.Fprintf(os.Stderr, " ^ i.e. %s\n", fr.locals[ptr].String()) switch deref(fr.locals[ptr].Var.Type()).(type) { case *types.Array, *types.Slice: if _, ok := fr.env.arrays[fr.env.globals[ptr]]; !ok { fr.env.arrays[fr.env.globals[ptr]] = make(Elems) } case *types.Struct: if _, ok := fr.env.structs[fr.env.globals[ptr]]; !ok { fr.env.structs[fr.env.globals[ptr]] = make(Fields) } } } switch vd, kind := fr.get(ptr); kind { case Array, LocalArray: fr.locals[val] = vd fmt.Fprintf(os.Stderr, " %s = *%s (array)\n", cyan(reg(val)), ptr.Name()) case Struct, LocalStruct: fr.locals[val] = vd fmt.Fprintf(os.Stderr, " %s = *%s (struct)\n", cyan(reg(val)), ptr.Name()) case Chan: fr.locals[val] = vd fmt.Fprintf(os.Stderr, " %s = *%s (previously initalised Chan)\n", cyan(reg(val)), ptr.Name()) case Nothing: fmt.Fprintf(os.Stderr, " # %s = *%s (not found)\n", red(inst.String()), red(inst.X.String())) if _, ok := val.Type().Underlying().(*types.Chan); ok { fmt.Fprintf(os.Stderr, " ^ channel (not allocated, must be initialised by MakeChan)") } default: fmt.Fprintf(os.Stderr, " # %s = *%s/%s (not found, type=%s)\n", red(inst.String()), red(inst.X.String()), reg(inst.X), inst.Type().String()) } } func visitSelect(s *ssa.Select, fr *frame) { if fr.gortn.leaf == nil { panic("Select: Session head Node cannot be nil") } fr.env.selNode[s] = struct { parent *sesstype.Node blocking bool }{ fr.gortn.leaf, s.Blocking, } for _, state := range s.States { locn := loc(fr, state.Chan.Pos()) switch vd, kind := fr.get(state.Chan); kind { case Chan: ch := fr.env.chans[vd] fmt.Fprintf(os.Stderr, " select "+orange("%s")+" (%d states)\n", vd.String(), len(s.States)) switch state.Dir { case types.SendOnly: fr.gortn.leaf = fr.env.selNode[s].parent fr.gortn.AddNode(sesstype.NewSelectSendNode(fr.gortn.role, *ch, state.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) case types.RecvOnly: fr.gortn.leaf = fr.env.selNode[s].parent fr.gortn.AddNode(sesstype.NewSelectRecvNode(*ch, fr.gortn.role, state.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) default: panic("Select: Cannot handle with SendRecv channels") } case Nothing: fr.printCallStack() panic(fmt.Sprintf("Select: Channel %s at %s is undefined", reg(state.Chan), locn)) default: fr.printCallStack() panic(fmt.Sprintf("Select: Channel %s at %s is of wrong kind", reg(state.Chan), locn)) } } if !s.Blocking { // Default state exists fr.gortn.leaf = fr.env.selNode[s].parent fr.gortn.AddNode(&sesstype.EmptyBodyNode{}) fmt.Fprintf(os.Stderr, " Default: %s\n", orange((*fr.gortn.leaf).String())) } } func visitReturn(ret *ssa.Return, fr *frame) []*utils.Definition { var vds []*utils.Definition for _, result := range ret.Results { vds = append(vds, fr.locals[result]) } return vds } // Handles function call. // Wrapper for calling visitFunc and performing argument translation. func visitCall(c *ssa.Call, caller *frame) { caller.call(c) } func visitIf(inst *ssa.If, fr *frame) { if len(inst.Block().Succs) != 2 { panic("If: Cannot handle If with more or less than 2 successor blocks!") } ifparent := fr.gortn.leaf if ifparent == nil { panic("If: Parent is nil") } if ch, isRecvTest := fr.env.recvTest[inst.Cond]; isRecvTest { fmt.Fprintf(os.Stderr, " @ Switch to recvtest true\n") fr.gortn.leaf = ifparent fr.gortn.AddNode(sesstype.NewRecvNode(*ch, fr.gortn.role, ch.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) visitBlock(inst.Block().Succs[0], fr) fmt.Fprintf(os.Stderr, " @ Switch to recvtest false\n") fr.gortn.leaf = ifparent fr.gortn.AddNode(sesstype.NewRecvStopNode(*ch, fr.gortn.role, ch.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) visitBlock(inst.Block().Succs[1], fr) } else if selTest, isSelTest := fr.env.selTest[inst.Cond]; isSelTest { // Check if this is a select-test-jump, if so handle separately. fmt.Fprintf(os.Stderr, " @ Switch to select branch #%d\n", selTest.idx) if selParent, ok := fr.env.selNode[selTest.tpl]; ok { fr.gortn.leaf = ifparent *fr.gortn.leaf = (*selParent.parent).Child(selTest.idx) visitBlock(inst.Block().Succs[0], fr) if !selParent.blocking && len((*selParent.parent).Children()) > selTest.idx+1 { *fr.gortn.leaf = (*selParent.parent).Child(selTest.idx + 1) } visitBlock(inst.Block().Succs[1], fr) } else { panic("Select without corresponding sesstype.Node") } } else { fr.env.ifparent.Push(*fr.gortn.leaf) parent := fr.env.ifparent.Top() fr.gortn.leaf = &parent fr.gortn.AddNode(&sesstype.EmptyBodyNode{}) visitBlock(inst.Block().Succs[0], fr) parent = fr.env.ifparent.Top() fr.gortn.leaf = &parent fr.gortn.AddNode(&sesstype.EmptyBodyNode{}) visitBlock(inst.Block().Succs[1], fr) fr.env.ifparent.Pop() } // This is end of the block so no continuation } func visitMakeChan(inst *ssa.MakeChan, caller *frame) { locn := loc(caller, inst.Pos()) role := caller.gortn.role vd := utils.NewDef(inst) // Unique identifier for inst ch := caller.env.session.MakeChan(vd, role) caller.env.chans[vd] = &ch caller.gortn.AddNode(sesstype.NewNewChanNode(ch)) caller.locals[inst] = vd fmt.Fprintf(os.Stderr, " New channel %s { type: %s } by %s at %s\n", green(ch.Name()), ch.Type(), vd.String(), locn) fmt.Fprintf(os.Stderr, " ^ in role %s\n", role.Name()) } func visitSend(send *ssa.Send, fr *frame) { locn := loc(fr, send.Chan.Pos()) if vd, kind := fr.get(send.Chan); kind == Chan { ch := fr.env.chans[vd] fr.gortn.AddNode(sesstype.NewSendNode(fr.gortn.role, *ch, send.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) } else if kind == Nothing { fr.locals[send.Chan] = utils.NewDef(send.Chan) ch := fr.env.session.MakeExtChan(fr.locals[send.Chan], fr.gortn.role) fr.env.chans[fr.locals[send.Chan]] = &ch fr.gortn.AddNode(sesstype.NewSendNode(fr.gortn.role, ch, send.Chan.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) fmt.Fprintf(os.Stderr, " ^ Send: Channel %s at %s is external\n", reg(send.Chan), locn) } else { fr.printCallStack() panic(fmt.Sprintf("Send: Channel %s at %s is of wrong kind", reg(send.Chan), locn)) } } func visitRecv(recv *ssa.UnOp, fr *frame) { locn := loc(fr, recv.X.Pos()) if vd, kind := fr.get(recv.X); kind == Chan { ch := fr.env.chans[vd] if recv.CommaOk { // ReceiveOK test fr.recvok[recv] = ch // TODO(nickng) technically this should do receive (both branches) } else { // Normal receive fr.gortn.AddNode(sesstype.NewRecvNode(*ch, fr.gortn.role, recv.X.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) } } else if kind == Nothing { fr.locals[recv.X] = utils.NewDef(recv.X) ch := fr.env.session.MakeExtChan(fr.locals[recv.X], fr.gortn.role) fr.env.chans[fr.locals[recv.X]] = &ch fr.gortn.AddNode(sesstype.NewRecvNode(ch, fr.gortn.role, recv.X.Type())) fmt.Fprintf(os.Stderr, " %s\n", orange((*fr.gortn.leaf).String())) fmt.Fprintf(os.Stderr, " ^ Recv: Channel %s at %s is external\n", reg(recv.X), locn) } else { fr.printCallStack() panic(fmt.Sprintf("Recv: Channel %s at %s is of wrong kind", reg(recv.X), locn)) } } // visitClose for the close() builtin primitive. func visitClose(ch sesstype.Chan, fr *frame) { fr.gortn.AddNode(sesstype.NewEndNode(ch)) } func visitJump(inst *ssa.Jump, fr *frame) { //fmt.Fprintf(os.Stderr, " -jump-> Block %d\n", inst.Block().Succs[0].Index) if len(inst.Block().Succs) != 1 { panic("Cannot Jump with multiple successors!") } visitBlock(inst.Block().Succs[0], fr) } func visitStore(inst *ssa.Store, fr *frame) { source := inst.Val dstPtr := inst.Addr // from Alloc or field/elem access if _, ok := dstPtr.(*ssa.Global); ok { vdOld, _ := fr.env.globals[dstPtr] switch vd, kind := fr.get(source); kind { case Array: fr.env.globals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " # store (global) *%s = %s of type %s\n", dstPtr.String(), source.Name(), source.Type().String()) case Struct: fr.env.globals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " # store (global) *%s = %s of type %s\n", reg(dstPtr), reg(source), source.Type().String()) default: fmt.Fprintf(os.Stderr, " # store (global) *%s = %s of type %s\n", red(reg(dstPtr)), reg(source), source.Type().String()) } } else { vdOld, _ := fr.get(dstPtr) switch vd, kind := fr.get(source); kind { case Array: // Pre: fr.locals[source] points to vd // Pre: fr.locals[dstPtr] points to (empty/outdated) vdOld // Post: fr.locals[source] unchanged // Post: fr.locals[dstPtr] points to vd fr.locals[dstPtr] = vd // was vdOld fr.updateDefs(vdOld, vd) // Update all references to vdOld to vd fmt.Fprintf(os.Stderr, " # store array *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case LocalArray: fr.locals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " store larray *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case Chan: fr.locals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " store chan *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case Struct: fr.locals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " store struct *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case LocalStruct: fr.locals[dstPtr] = vd fr.updateDefs(vdOld, vd) fmt.Fprintf(os.Stderr, " store lstruct *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case Untracked: fr.locals[dstPtr] = vd fmt.Fprintf(os.Stderr, " store update *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) case Nothing: fmt.Fprintf(os.Stderr, " # store *%s = %s of type %s\n", red(reg(dstPtr)), reg(source), source.Type().String()) default: fr.locals[dstPtr] = vd fmt.Fprintf(os.Stderr, " store *%s = %s of type %s\n", cyan(reg(dstPtr)), reg(source), source.Type().String()) } } } func visitChangeType(inst *ssa.ChangeType, fr *frame) { switch vd, kind := fr.get(inst.X); kind { case Chan: fr.locals[inst] = vd // ChangeType from <-chan and chan<- ch := fr.env.chans[vd] fmt.Fprintf(os.Stderr, " & changetype from %s to %s (channel %s)\n", green(reg(inst.X)), reg(inst), ch.Name()) fmt.Fprintf(os.Stderr, " ^ origin\n") case Nothing: fmt.Fprintf(os.Stderr, " # changetype %s = %s %s\n", inst.Name(), inst.X.Name(), inst.String()) fmt.Fprintf(os.Stderr, " ^ unknown kind\n") default: fr.locals[inst] = vd fmt.Fprintf(os.Stderr, " # changetype %s = %s\n", red(inst.Name()), inst.String()) } } func visitChangeInterface(inst *ssa.ChangeInterface, fr *frame) { fr.locals[inst] = fr.locals[inst.X] fmt.Fprintf(os.Stderr, " # changeinterface %s = %s\n", reg(inst), inst.String()) } func visitBinOp(inst *ssa.BinOp, fr *frame) { switch inst.Op { case token.EQL: if selTuple, isSelTuple := fr.env.selIdx[inst.X]; isSelTuple { branchID := int(inst.Y.(*ssa.Const).Int64()) fr.env.selTest[inst] = struct { idx int tpl ssa.Value }{ branchID, selTuple, } } else { fmt.Fprintf(os.Stderr, " # %s = "+red("%s")+"\n", inst.Name(), inst.String()) } default: fmt.Fprintf(os.Stderr, " # %s = "+red("%s")+"\n", inst.Name(), inst.String()) } } func visitMakeInterface(inst *ssa.MakeInterface, fr *frame) { switch vd, kind := fr.get(inst.X); kind { case Struct, LocalStruct: fmt.Fprintf(os.Stderr, " %s <-(struct/iface)- %s %s = %s\n", cyan(reg(inst)), reg(inst.X), inst.String(), vd.String()) fr.locals[inst] = vd case Array, LocalArray: fmt.Fprintf(os.Stderr, " %s <-(array/iface)- %s %s = %s\n", cyan(reg(inst)), reg(inst.X), inst.String(), vd.String()) fr.locals[inst] = vd default: fmt.Fprintf(os.Stderr, " # %s <- %s\n", red(reg(inst)), inst.String()) } } func visitSlice(inst *ssa.Slice, fr *frame) { fr.env.arrays[utils.NewDef(inst)] = make(Elems) } func visitMakeSlice(inst *ssa.MakeSlice, fr *frame) { fr.env.arrays[utils.NewDef(inst)] = make(Elems) } func visitFieldAddr(inst *ssa.FieldAddr, fr *frame) { field := inst struc := inst.X index := inst.Field if stype, ok := deref(struc.Type()).Underlying().(*types.Struct); ok { switch vd, kind := fr.get(struc); kind { case Struct: fmt.Fprintf(os.Stderr, " %s = %s(=%s)->[%d] of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.env.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.env.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := deref(field.Type()).Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating)\n", field.Name()) } } else if fr.env.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.env.structs[vd][index] case LocalStruct: fmt.Fprintf(os.Stderr, " %s = %s(=%s)->[%d] (local) of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := deref(field.Type()).Underlying().(*types.Struct); ok { fr.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating locally)\n", field.Name()) } } else if fr.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.structs[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = %s(=%s)->[%d] (external) of type %s\n", cyan(reg(field)), inst.X.Name(), vd.String(), index, field.Type().String()) vd := utils.NewDef(struc) // New external struct fr.locals[struc] = vd fr.env.structs[vd] = make(Fields, stype.NumFields()) vdField := utils.NewDef(field) // New external field fr.env.structs[vd][index] = vdField fr.locals[field] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition of type %s\n", field.Name(), inst.Type().(*types.Pointer).Elem().Underlying().String()) // If field is struct if fieldType, ok := deref(field.Type()).Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } default: panic(fmt.Sprintf("FieldAddr: Cannot access non-struct %s %T %d", reg(struc), deref(struc.Type()).Underlying(), kind)) } } else { panic(fmt.Sprintf("FieldAddr: Cannot access field - %s not a struct\n", reg(struc))) } } func visitField(inst *ssa.Field, fr *frame) { field := inst struc := inst.X index := inst.Field if stype, ok := struc.Type().Underlying().(*types.Struct); ok { switch vd, kind := fr.get(struc); kind { case Struct: fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.env.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.env.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating)\n", field.Name()) } } else if fr.env.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.env.structs[vd][index] case LocalStruct: fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] (local) of type %s\n", cyan(reg(field)), struc.Name(), vd.String(), index, field.Type().String()) if fr.structs[vd][index] == nil { // First use vdField := utils.NewDef(field) fr.structs[vd][index] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition\n", field.Name()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s is a struct (allocating locally)\n", field.Name()) } } else if fr.structs[vd][index].Var != field { // Previously defined fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.structs[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[field] = fr.structs[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = %s(=%s).[%d] (external) of type %s\n", cyan(reg(field)), inst.X.Name(), vd.String(), index, field.Type().String()) vd := utils.NewDef(struc) // New external struct fr.locals[struc] = vd fr.env.structs[vd] = make(Fields, stype.NumFields()) vdField := utils.NewDef(field) // New external field fr.env.structs[vd][index] = vdField fr.locals[field] = vdField fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as field definition of type %s\n", field.Name(), inst.Type().Underlying().String()) // If field is struct if fieldType, ok := field.Type().Underlying().(*types.Struct); ok { fr.env.structs[vdField] = make(Fields, fieldType.NumFields()) fmt.Fprintf(os.Stderr, " ^ field %s previously defined as %s\n", field.Name(), reg(fr.env.structs[vd][index].Var)) } default: panic(fmt.Sprintf("Field: Cannot access non-struct %s %T %d", reg(struc), struc.Type(), kind)) } } else { panic(fmt.Sprintf("Field: Cannot access field - %s not a struct\n", reg(struc))) } } func visitIndexAddr(inst *ssa.IndexAddr, fr *frame) { elem := inst array := inst.X index := inst.Index _, isArray := deref(array.Type()).Underlying().(*types.Array) _, isSlice := deref(array.Type()).Underlying().(*types.Slice) if isArray || isSlice { switch vd, kind := fr.get(array); kind { case Array: fmt.Fprintf(os.Stderr, " %s = &%s(=%s)[%d] of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.env.arrays[vd][index] == nil { // First use vdelem := utils.NewDef(elem) fr.env.arrays[vd][index] = vdelem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.env.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.env.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.env.arrays[vd][index] case LocalArray: fmt.Fprintf(os.Stderr, " %s = &%s(=%s)[%d] (local) of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.arrays[vd][index] == nil { // First use vdElem := utils.NewDef(elem) fr.arrays[vd][index] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.arrays[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = &%s(=%s)[%d] (external) of type %s\n", cyan(reg(elem)), inst.X.Name(), vd.String(), index, elem.Type().String()) vd := utils.NewDef(array) // New external array fr.locals[array] = vd fr.env.arrays[vd] = make(Elems) vdElem := utils.NewDef(elem) // New external elem fr.env.arrays[vd][index] = vdElem fr.locals[elem] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition of type %s\n", elem.Name(), inst.Type().(*types.Pointer).Elem().Underlying().String()) default: panic(fmt.Sprintf("IndexAddr: Cannot access non-array %s", reg(array))) } } else { panic(fmt.Sprintf("IndexAddr: Cannot access field - %s not an array", reg(array))) } } func visitIndex(inst *ssa.Index, fr *frame) { elem := inst array := inst.X index := inst.Index _, isArray := array.Type().Underlying().(*types.Array) _, isSlice := array.Type().Underlying().(*types.Slice) if isArray || isSlice { switch vd, kind := fr.get(array); kind { case Array: fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.env.arrays[vd][index] == nil { // First use vdelem := utils.NewDef(elem) fr.env.arrays[vd][index] = vdelem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.env.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.env.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.env.arrays[vd][index] case LocalArray: fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] (local) of type %s\n", cyan(reg(elem)), array.Name(), vd.String(), index, elem.Type().String()) if fr.arrays[vd][index] == nil { // First use vdElem := utils.NewDef(elem) fr.arrays[vd][index] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition\n", elem.Name()) } else if fr.arrays[vd][index].Var != elem { // Previously defined fmt.Fprintf(os.Stderr, " ^ elem %s previously defined as %s\n", elem.Name(), reg(fr.arrays[vd][index].Var)) } // else Accessed before (and unchanged) fr.locals[elem] = fr.arrays[vd][index] case Nothing, Untracked: // Nothing: Very likely external struct. // Untracked: likely branches of return values (e.g. returning nil) fmt.Fprintf(os.Stderr, " %s = %s(=%s)[%d] (external) of type %s\n", cyan(reg(elem)), inst.X.Name(), vd.String(), index, elem.Type().String()) vd := utils.NewDef(array) // New external array fr.locals[array] = vd fr.env.arrays[vd] = make(Elems) vdElem := utils.NewDef(elem) // New external elem fr.env.arrays[vd][index] = vdElem fr.locals[elem] = vdElem fmt.Fprintf(os.Stderr, " ^ accessed for the first time: use %s as elem definition of type %s\n", elem.Name(), inst.Type().(*types.Pointer).Elem().Underlying().String()) default: panic(fmt.Sprintf("Index: Cannot access non-array %s", reg(array))) } } else { panic(fmt.Sprintf("Index: Cannot access element - %s not an array", reg(array))) } } func visitDefer(inst *ssa.Defer, fr *frame) { fr.defers = append(fr.defers, inst) } func visitRunDefers(inst *ssa.RunDefers, fr *frame) { for i := len(fr.defers) - 1; i >= 0; i-- { fr.callCommon(fr.defers[i].Value(), fr.defers[i].Common()) } } func visitPhi(inst *ssa.Phi, fr *frame) { // In the case of channels, find the last defined channel and replace it. if _, ok := inst.Type().(*types.Chan); ok { //preds := inst.Block().Preds // PredBlocks: order is significant. fr.locals[inst], _ = fr.get(inst.Edges[0]) fr.phi[inst] = inst.Edges } } func visitTypeAssert(inst *ssa.TypeAssert, fr *frame) { if iface, ok := inst.AssertedType.(*types.Interface); ok { if meth, _ := types.MissingMethod(inst.X.Type(), iface, true); meth == nil { // No missing methods switch vd, kind := fr.get(inst.X); kind { case Struct, LocalStruct, Array, LocalArray, Chan: fr.tuples[inst] = make(Tuples, 2) fr.tuples[inst][0] = vd fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s) iface\n", reg(inst), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ defined as %s\n", vd.String()) default: fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s)\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ untracked/unknown\n") } return } } else { // Concrete type if types.Identical(inst.AssertedType.Underlying(), inst.X.Type().Underlying()) { switch vd, kind := fr.get(inst.X); kind { case Struct, LocalStruct, Array, LocalArray, Chan: fr.tuples[inst] = make(Tuples, 2) fr.tuples[inst][0] = vd fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s) concrete\n", reg(inst), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ defined as %s\n", vd.String()) default: fmt.Fprintf(os.Stderr, " %s = %s.(type assert %s)\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) fmt.Fprintf(os.Stderr, " ^ untracked/unknown\n") } return } } fmt.Fprintf(os.Stderr, " # %s = %s.(%s) impossible type assertion\n", red(reg(inst)), reg(inst.X), inst.AssertedType.String()) } ================================================ FILE: cmd/buildssa.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "log" "os" "github.com/nickng/dingo-hunter/logwriter" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/spf13/cobra" ) // buildssaCmd represents the buildssa command var buildssaCmd = &cobra.Command{ Use: "buildssa", Short: "Build SSA IR of the input source files", Long: `Build SSA IR of the input source files`, Run: func(cmd *cobra.Command, args []string) { Build(args) }, } var ( dumpSSA bool dumpAll bool ) func init() { RootCmd.AddCommand(buildssaCmd) buildssaCmd.Flags().BoolVar(&dumpSSA, "dump", false, "dump SSA IR of input files (based on CFG)") buildssaCmd.Flags().BoolVar(&dumpAll, "dump-all", false, "dump all SSA IR of input files (including unused)") if dumpSSA && dumpAll { dumpSSA = false // dumpAll override dumpSSA } } func Build(files []string) { logFile, err := RootCmd.PersistentFlags().GetString("log") if err != nil { log.Fatal(err) } noLogging, err := RootCmd.PersistentFlags().GetBool("no-logging") if err != nil { log.Fatal(err) } noColour, err := RootCmd.PersistentFlags().GetBool("no-colour") if err != nil { log.Fatal(err) } l := logwriter.NewFile(logFile, !noLogging, !noColour) if err := l.Create(); err != nil { log.Fatal(err) } defer l.Cleanup() conf, err := ssabuilder.NewConfig(files) if err != nil { log.Fatal(err) } conf.BuildLog = l.Writer ssainfo, err := conf.Build() if err != nil { log.Fatal(err) } if dumpSSA { if _, err := ssainfo.WriteTo(os.Stdout); err != nil { log.Fatal(err) } } if dumpAll { if _, err := ssainfo.WriteAll(os.Stdout); err != nil { log.Fatal(err) } } } ================================================ FILE: cmd/cfsms.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "log" "github.com/nickng/dingo-hunter/cfsmextract" "github.com/nickng/dingo-hunter/logwriter" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/spf13/cobra" ) var ( prefix string // Output files prefix outdir string // CFMSs output directory ) // cfsmsCmd represents the analyse command var cfsmsCmd = &cobra.Command{ Use: "cfsms", Short: "Extract CFSMs from source code", Long: `Extract CFSMs from source code The inputs should be a list of .go files in the same directory (of package main) One of the .go file should contain the main function.`, Run: func(cmd *cobra.Command, args []string) { extractCFSMs(args) }, } func init() { cfsmsCmd.Flags().StringVar(&prefix, "prefix", "output", "Output files prefix") cfsmsCmd.Flags().StringVar(&outdir, "outdir", "third_party/gmc-synthesis/inputs", "Output directory for CFSMs") RootCmd.AddCommand(cfsmsCmd) } func extractCFSMs(files []string) { logFile, err := RootCmd.PersistentFlags().GetString("log") if err != nil { log.Fatal(err) } noLogging, err := RootCmd.PersistentFlags().GetBool("no-logging") if err != nil { log.Fatal(err) } noColour, err := RootCmd.PersistentFlags().GetBool("no-colour") if err != nil { log.Fatal(err) } l := logwriter.NewFile(logFile, !noLogging, !noColour) if err := l.Create(); err != nil { log.Fatal(err) } defer l.Cleanup() conf, err := ssabuilder.NewConfig(files) conf.BuildLog = l.Writer if err != nil { log.Fatal(err) } ssainfo, err := conf.Build() if err != nil { log.Fatal(err) } extract := cfsmextract.New(ssainfo, prefix, outdir) go extract.Run() select { case <-extract.Error: log.Fatal(err) case <-extract.Done: log.Println("Analysis finished in", extract.Time) extract.WriteOutput() } } ================================================ FILE: cmd/checkfair.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "log" "github.com/nickng/dingo-hunter/fairness" "github.com/nickng/dingo-hunter/logwriter" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/spf13/cobra" ) // checkfairCmd represents the check-fair command var checkfairCmd = &cobra.Command{ Use: "checkfair", Short: "Runs loop fairness checks", Long: `Runs loop fairness checks The checks will find potential problematic loops, such as those which are unbalanced - loop conditions favour infinite iterations deterministically`, Run: func(cmd *cobra.Command, args []string) { check(args) }, } func init() { RootCmd.AddCommand(checkfairCmd) } func check(files []string) { logFile, err := RootCmd.PersistentFlags().GetString("log") if err != nil { log.Fatal(err) } noLogging, err := RootCmd.PersistentFlags().GetBool("no-logging") if err != nil { log.Fatal(err) } noColour, err := RootCmd.PersistentFlags().GetBool("no-colour") if err != nil { log.Fatal(err) } l := logwriter.NewFile(logFile, !noLogging, !noColour) if err := l.Create(); err != nil { log.Fatal(err) } defer l.Cleanup() conf, err := ssabuilder.NewConfig(files) if err != nil { log.Fatal(err) } conf.BuildLog = l.Writer ssainfo, err := conf.Build() if err != nil { log.Fatal(err) } fairness.Check(ssainfo) } ================================================ FILE: cmd/migo.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "log" "os" "github.com/nickng/dingo-hunter/logwriter" "github.com/nickng/dingo-hunter/migoextract" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/nickng/migo/v3/migoutil" "github.com/spf13/cobra" ) var ( outfile string // Path to output file ) // migoCmd represents the analyse command var migoCmd = &cobra.Command{ Use: "migo", Short: "Extract MiGo types from source code", Long: `Extract MiGo types from source code The inputs should be a list of .go files in the same directory (of package main) One of the .go file should contain the main function.`, Run: func(cmd *cobra.Command, args []string) { extractMigo(args) }, } func init() { migoCmd.Flags().StringVar(&outfile, "output", "", "output migo file") RootCmd.AddCommand(migoCmd) } func extractMigo(files []string) { logFile, err := RootCmd.PersistentFlags().GetString("log") if err != nil { log.Fatal(err) } noLogging, err := RootCmd.PersistentFlags().GetBool("no-logging") if err != nil { log.Fatal(err) } noColour, err := RootCmd.PersistentFlags().GetBool("no-colour") if err != nil { log.Fatal(err) } l := logwriter.NewFile(logFile, !noLogging, !noColour) if err := l.Create(); err != nil { log.Fatal(err) } defer l.Cleanup() conf, err := ssabuilder.NewConfig(files) if err != nil { log.Fatal(err) } conf.BuildLog = l.Writer ssainfo, err := conf.Build() if err != nil { log.Fatal(err) } extract, err := migoextract.New(ssainfo, l.Writer) if err != nil { log.Fatal(err) } go extract.Run() select { case <-extract.Error: log.Fatal(err) case <-extract.Done: extract.Logger.Println("Analysis finished in", extract.Time) } migoutil.SimplifyProgram(extract.Env.MigoProg) if outfile != "" { f, err := os.Create(outfile) if err != nil { log.Fatal(err) } f.WriteString(extract.Env.MigoProg.String()) defer f.Close() } else { os.Stdout.WriteString(extract.Env.MigoProg.String()) } } ================================================ FILE: cmd/root.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "fmt" "os" "github.com/spf13/cobra" "github.com/spf13/viper" ) var ( cfgFile string // Path to config file logFile string // Path to log file noLogging bool // Turn off logging noColour bool // Turn of colour output ) // RootCmd represents the base command when called without any subcommands var RootCmd = &cobra.Command{ Use: "dingo-hunter", Short: "Static analyser for deadlock detection", Long: `dingo-hunter is a static deadlock detector for Go This is the toplevel command. Use "dingo-hunter [command] sources.go..." to analyse source files`, } // Execute adds all child commands to the root command sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { if err := RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) } } func init() { cobra.OnInitialize(initConfig) RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dingo-hunter.yaml)") RootCmd.PersistentFlags().StringVar(&logFile, "log", "", "path to log file (default is stdout)") RootCmd.PersistentFlags().BoolVar(&noLogging, "no-logging", false, "disable logging") RootCmd.PersistentFlags().BoolVar(&noColour, "no-colour", false, "disable colour output") } // initConfig reads in config file and ENV variables if set. func initConfig() { if cfgFile != "" { // enable ability to specify config file via flag viper.SetConfigFile(cfgFile) } viper.SetConfigName(".dingo-hunter") // name of config file (without extension) viper.AddConfigPath("$HOME") // adding home directory as first search path viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { fmt.Println("Using config file:", viper.ConfigFileUsed()) } } ================================================ FILE: cmd/serve.go ================================================ // Copyright © 2016 Nicholas Ng // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package cmd import ( "go/build" "log" "path" "github.com/nickng/dingo-hunter/webservice" "github.com/spf13/cobra" ) // serveCmd represents the serve command var serveCmd = &cobra.Command{ Use: "serve", Aliases: []string{"server"}, Short: "Run an HTTP webservice for demo", Long: `Run an HTTP webservice for analysis demo. The analysis will be presented side-by-side with its source code. Each example is a Go command (i.e. package main) in a directory, under the examples directory.`, Run: func(cmd *cobra.Command, args []string) { Serve() }, } const basePkg = "github.com/nickng/dingo-hunter" var ( addr string // Listen interface. port string // Listen port. ) func init() { RootCmd.AddCommand(serveCmd) p, err := build.Default.Import(basePkg, "", build.FindOnly) if err != nil { log.Fatal("Could not find base path") } basePath := p.Dir serveCmd.Flags().StringVar(&addr, "bind", "127.0.0.1", "Bind address. Defaults to 127.0.0.1.") serveCmd.Flags().StringVar(&port, "port", "6060", "Listen port. Defaults to 6060.") serveCmd.Flags().StringVar(&webservice.ExamplesDir, "examples", path.Join(basePath, "examples", "popl17"), "Path to examples directory") serveCmd.Flags().StringVar(&webservice.TemplateDir, "templates", path.Join(basePath, "templates"), "Path to templates directory") serveCmd.Flags().StringVar(&webservice.StaticDir, "static", path.Join(basePath, "static"), "Path to static files directory") } // Serve starts the HTTP server. func Serve() { server := webservice.NewServer(addr, port) server.Start() server.Close() } ================================================ FILE: doc.go ================================================ // Command dingo-hunter is a tool for analysing Go code to extract the // communication patterns for deadlock analysis. // // The deadlock analysis approach is based on multiparty session types and // global graph synthesis POPL'15 (Lange, Tuosto, Yoshida) and CC'16 (Ng, // Yoshida). package main ================================================ FILE: examples/altbit/main.go ================================================ package main // Alternating bit - from Milner's Communication and Concurrency // with (meaningless) timing import ( "fmt" "time" ) func main() { trans := make(chan int, 1) ack := make(chan int, 1) go tx(trans, ack) rx(ack, trans) } func flip(b int) int { return (b + 1) % 2 } func tx(snd chan<- int, ack <-chan int) { b := 0 for { fmt.Printf("tx[%d]: accept\n", b) fmt.Printf("tx[%d]: send[%d]\n", b, b) snd <- b SENDING: for { // SENDING[b] select { case x := <-ack: if x == b { fmt.Printf("tx[%d]: ack[b]\n", b) b = flip(b) break SENDING // ACCEPT !b } else { fmt.Printf("tx[%d]: ack[!b]\n", b) b = flip(b) // SENDING b } case <-time.After(1 * time.Second): fmt.Printf("tx[%d]: timeout\n", b) fmt.Printf("tx[%d]: send[%d]\n", b, b) snd <- b // SENDING b } } } } func rx(reply chan<- int, trans <-chan int) { b := 1 for { fmt.Printf("rx[%d]: deliver\n", b) fmt.Printf("rx[%d]: reply[%d]\n", b, b) reply <- b REPLYING: for { select { // REPLYING[b] case x := <-trans: if x != b { fmt.Printf("rx[%d]: trans[!b]\n", b) break REPLYING // DELIVER !b } else { fmt.Printf("rx[%d]: trans[b]\n", b) // REPLYING b } case <-time.After(1 * time.Second): fmt.Printf("rx[%d]: timeout\n", b) fmt.Printf("rx[%d]: reply[%d]\n", b, b) reply <- b // REPLYING b } } } } ================================================ FILE: examples/branch-dependent-deadlock/main.go ================================================ package main func S(ch chan int, done chan struct{}) { ch <- 1 done <- struct{}{} } func R(ch chan int, done chan struct{}) { <-ch done <- struct{}{} } func main() { done := make(chan struct{}) for i := 0; i < 10; i++ { ch := make(chan int) if i%2 == 0 { go S(ch, done) } else { go R(ch, done) } } <-done } ================================================ FILE: examples/channel-scoping-test/main.go ================================================ // The reentry example targets multiple use of a function with channel created // inside. The channel is anchored in the function, so multiple calls of the // function will use different version of the channel(s). Combined with loop // indices assumptions this will be inaccurate. package main func main() { ch := makenew() for i := 0; i < 2; i++ { ch2 := makenew() ch2 <- 22 } ch <- 42 } func makenew() chan int { return make(chan int, 1) } ================================================ FILE: examples/commaok/main.go ================================================ package main import ( "fmt" ) func main() { ch := make(chan int, 1) ch <- 1 y := make(map[int]int) if x, ok := <-ch; ok { y[x] = 1 if z, ok := y[1]; ok { fmt.Println(z) } } } ================================================ FILE: examples/cond-recur/main.go ================================================ // Command conditional-recur has a recursion with conditional on one goroutine // and another receiving until a done message is received. package main import "fmt" func x(ch chan int, done chan struct{}) { i := 0 for { if i < 3 { ch <- i fmt.Println("Sent", i) i++ } else { done <- struct{}{} return } } } func main() { done := make(chan struct{}) ch := make(chan int) go x(ch, done) FINISH: for { select { case x := <-ch: fmt.Println(x) case <-done: break FINISH } } } ================================================ FILE: examples/deadlocking-philosophers/main.go ================================================ package main // Dining Philosopher modified to include a deadlock. // https://github.com/doug/go-dining-philosophers import ( "fmt" "math/rand" "time" ) type Philosopher struct { name string chopstick chan bool neighbor *Philosopher } func makePhilosopher(name string, neighbor *Philosopher) *Philosopher { phil := &Philosopher{name, make(chan bool, 1), neighbor} phil.chopstick <- true return phil } func (phil *Philosopher) think() { fmt.Printf("%v is thinking.\n", phil.name) time.Sleep(time.Duration(rand.Int63n(1e9))) } func (phil *Philosopher) eat() { fmt.Printf("%v is eating.\n", phil.name) time.Sleep(time.Duration(rand.Int63n(1e9))) } func (phil *Philosopher) getChopsticks() { timeout := make(chan bool, 1) go func() { time.Sleep(1e9); timeout <- true }() <-phil.chopstick fmt.Printf("%v got his chopstick.\n", phil.name) // XXX Deadlock may happen here. fmt.Printf("%v got %v's chopstick.\n", phil.name, phil.neighbor.name) fmt.Printf("%v has two chopsticks.\n", phil.name) return } func (phil *Philosopher) returnChopsticks() { phil.chopstick <- true phil.neighbor.chopstick <- true } func (phil *Philosopher) dine(announce chan *Philosopher) { phil.think() phil.getChopsticks() phil.eat() phil.returnChopsticks() announce <- phil } func main() { //names := []string{"Kant", "Heidegger", "Wittgenstein", // "Locke", "Descartes", "Newton", "Hume", "Leibniz"} names := []string{"Phil"} philosophers := make([]*Philosopher, len(names)) var phil *Philosopher for i, name := range names { phil = makePhilosopher(name, phil) philosophers[i] = phil } philosophers[0].neighbor = phil fmt.Printf("There are %v philosophers sitting at a table.\n", len(philosophers)) fmt.Println("They each have one chopstick, and must borrow from their neighbor to eat.") announce := make(chan *Philosopher) for _, phil := range philosophers { go phil.dine(announce) } for i := 0; i < len(names); i++ { phil := <-announce fmt.Printf("%v is done dining.\n", phil.name) } } ================================================ FILE: examples/dining-philosophers/main.go ================================================ package main // Dining Philosopher. // https://github.com/doug/go-dining-philosophers import ( "fmt" "math/rand" "time" ) type Philosopher struct { name string chopstick chan bool neighbor *Philosopher } func makePhilosopher(name string, neighbor *Philosopher) *Philosopher { phil := &Philosopher{name, make(chan bool, 1), neighbor} phil.chopstick <- true return phil } func (phil *Philosopher) think() { fmt.Printf("%v is thinking.\n", phil.name) time.Sleep(time.Duration(rand.Int63n(1e9))) } func (phil *Philosopher) eat() { fmt.Printf("%v is eating.\n", phil.name) time.Sleep(time.Duration(rand.Int63n(1e9))) } func (phil *Philosopher) getChopsticks() { timeout := make(chan bool, 1) go func() { time.Sleep(1e9); timeout <- true }() <-phil.chopstick fmt.Printf("%v got his chopstick.\n", phil.name) select { case <-phil.neighbor.chopstick: fmt.Printf("%v got %v's chopstick.\n", phil.name, phil.neighbor.name) fmt.Printf("%v has two chopsticks.\n", phil.name) return case <-timeout: phil.chopstick <- true phil.think() phil.getChopsticks() } } func (phil *Philosopher) returnChopsticks() { phil.chopstick <- true phil.neighbor.chopstick <- true } func (phil *Philosopher) dine(announce chan *Philosopher) { phil.think() phil.getChopsticks() phil.eat() phil.returnChopsticks() announce <- phil } func main() { names := []string{"Kant", "Heidegger", "Wittgenstein", "Locke", "Descartes", "Newton", "Hume", "Leibniz"} philosophers := make([]*Philosopher, len(names)) var phil *Philosopher for i, name := range names { phil = makePhilosopher(name, phil) philosophers[i] = phil } philosophers[0].neighbor = phil fmt.Printf("There are %v philosophers sitting at a table.\n", len(philosophers)) fmt.Println("They each have one chopstick, and must borrow from their neighbor to eat.") announce := make(chan *Philosopher) for _, phil := range philosophers { go phil.dine(announce) } for i := 0; i < len(names); i++ { phil := <-announce fmt.Printf("%v is done dining.\n", phil.name) } } ================================================ FILE: examples/factorial/main.go ================================================ package main import "fmt" func main() { ch := make(chan int) go fact(5, ch) fmt.Println(<-ch) } func fact(n int, results chan<- int) { if n <= 1 { results <- n return } ch := make(chan int) go fact(n-1, ch) results <- n * <-ch } ================================================ FILE: examples/fanin-pattern/main.go ================================================ package main import ( "fmt" ) func work(out chan<- int) { for { out <- 42 } } func fanin(ch1, ch2 <-chan int) <-chan int { c := make(chan int) go func() { for { select { case s := <-ch1: c <- s case s := <-ch2: c <- s } } }() return c } func main() { input1 := make(chan int) input2 := make(chan int) go work(input1) go work(input2) c := fanin(input1, input2) for { fmt.Println(<-c) } } ================================================ FILE: examples/fanin-pattern-commaok/main.go ================================================ package main // fanin pattern, using for-range loop to consume values (syntactic sugar of // loop over r, ok := <-ch) import ( "fmt" "time" ) func work(out chan<- int) { for { out <- 42 } } func fanin(ch1, ch2 <-chan int) <-chan int { c := make(chan int) go func(ch1, ch2 <-chan int, c chan<- int) { for { select { case s := <-ch1: c <- s case s := <-ch2: c <- s default: close(c) return } } }(ch1, ch2, c) return c } func main() { input1 := make(chan int) input2 := make(chan int) go work(input1) go work(input2) time.Sleep(1 * time.Second) for v := range fanin(input1, input2) { fmt.Println(v) } } ================================================ FILE: examples/fcall/main.go ================================================ package main import "fmt" var ch chan int func f() { fmt.Println("blah") g() } func g() { <-ch fmt.Println("blah-g") } func main() { fmt.Println("before") ch = make(chan int) f() g() x := func() <-chan int { return make(chan int) } select { case <-x(): } fmt.Println("after") } ================================================ FILE: examples/forselect/main.go ================================================ // Command nodet-for-select is a for-select pattern between two compatible // recursive select. package main import "fmt" func sel1(ch1, ch2 chan int, done chan struct{}) { for { select { case <-ch1: fmt.Println("sel1: recv") done <- struct{}{} return case ch2 <- 1: fmt.Println("sel1: send") } } } func sel2(ch1, ch2 chan int, done chan struct{}) { for { select { case <-ch2: fmt.Println("sel2: recv") case ch1 <- 2: fmt.Println("sel2: send") done <- struct{}{} return } } } func main() { done := make(chan struct{}) a := make(chan int) b := make(chan int) go sel1(a, b, done) go sel2(a, b, done) <-done <-done } ================================================ FILE: examples/giachino-concur14-dining-philosopher/main.go ================================================ package main // Example from CONCUR 14 paper by Giachino et al. // doi: 10.1007/978-3-662-44584-6_6 import ( "fmt" "time" ) func Fork(fork chan int) { for { fork <- 1 <-fork } } // philosophers (infinite recursive). func phil(fork1, fork2 chan int, id int) { var x1, x2 int for { select { case x1 = <-fork1: select { case x2 = <-fork2: fmt.Printf("phil %d got both fork\n", id) fork1 <- x1 fork2 <- x2 default: fork1 <- x1 } case x1 = <-fork2: select { case x2 = <-fork1: fmt.Printf("phil %d got both fork\n", id) fork2 <- x1 fork1 <- x2 default: fork2 <- x1 } } } } func main() { fork1 := make(chan int) fork2 := make(chan int) fork3 := make(chan int) go Fork(fork1) go Fork(fork2) go Fork(fork3) go phil(fork1, fork2, 0) // deadlock if phil(fork2, fork1, 0) go phil(fork2, fork3, 1) go phil(fork3, fork1, 2) time.Sleep(10 * time.Second) } ================================================ FILE: examples/giachino-concur14-factorial/main.go ================================================ package main import "fmt" // Example from CONCUR 14 paper by Giachino et al. // doi: 10.1007/978-3-662-44584-6_6 func fact(n int, r, s chan int) { if n == 0 { m := <-r s <- m return } t := make(chan int, 1) fact(n-1, t, s) m := <-r t <- m * n } func main() { accumulated, result := make(chan int, 1), make(chan int) go fact(3, accumulated, result) accumulated <- 1 fmt.Println(<-result) } ================================================ FILE: examples/github-golang-go-issue-12734/main.go ================================================ package main // This is a bug report from golang/go which the deadlock detector does not // detect a deadlock because the net pacakge is loaded (disables detector). import ( "fmt" "net/http" ) func useless(address string) []byte { http.Get("https://www.google.com/") return nil } func test_a(test_channel chan int) { test_channel <- 1 return } func test() { test_channel := make(chan int) for i := 0; i < 10; i++ { go test_a(test_channel) } for { fmt.Println(<-test_channel) } } func main() { test() } ================================================ FILE: examples/golang-blog-prime-sieve/main.go ================================================ // Command golang-blog-prime-sieve is an example from Golang blog. // https://golang.org/doc/play/sieve.go // package main import ( "fmt" ) // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in <-chan int, out chan<- int, prime int) { for { i := <-in // Receive value from 'in'. if i%prime != 0 { out <- i // Send 'i' to 'out'. } } } // The prime sieve: Daisy-chain Filter processes. func main() { ch := make(chan int) // Create a new channel. go Generate(ch) // Launch Generate goroutine. for i := 0; i < 10; i++ { prime := <-ch fmt.Println(prime) ch1 := make(chan int) go Filter(ch, ch1, prime) ch = ch1 } } ================================================ FILE: examples/infinite-prime-sieve/main.go ================================================ // Command infinite-primesieve is a modified primesieve example from Golang // blog. The program generates an infinite list (instead of a fixed number) of // prime numbers. // // Original: https://golang.org/doc/play/sieve.go // package main import ( "fmt" ) // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in <-chan int, out chan<- int, prime int) { for { i := <-in // Receive value from 'in'. if i%prime != 0 { out <- i // Send 'i' to 'out'. } } } // The prime sieve: Daisy-chain Filter processes. func main() { ch := make(chan int) // Create a new channel. go Generate(ch) // Launch Generate goroutine. for i := 0; ; i++ { prime := <-ch fmt.Println(prime) ch1 := make(chan int) go Filter(ch, ch1, prime) ch = ch1 } } ================================================ FILE: examples/issue-10-close-wrong-migo-chan-name/main.go ================================================ package main // Issue #10, when generating migo from programs that uses close, the channel // name used is incorrect. func main() { x := make(chan bool) close(x) } ================================================ FILE: examples/issue-11-non-communicating-fn-call/main.go ================================================ package main // Issue #11, when a function is empty, all 'call' on that def in a migo file // should be scrubbed. func main() { x := make(chan bool) go func() { x <- true }() if true { <-x } } ================================================ FILE: examples/jobsched/main.go ================================================ package main import ( "fmt" "time" ) var i int func worker(id int, jobQueue <-chan int, done <-chan struct{}) { for { select { case jobID := <-jobQueue: fmt.Println(id, "Executing job", jobID) case <-done: fmt.Println(id, "Quits") return } } } func morejob() bool { i++ return i < 20 } func producer(q chan int, done chan struct{}) { for morejob() { q <- i } close(done) } func main() { jobQueue := make(chan int) done := make(chan struct{}) go worker(1, jobQueue, done) go worker(2, jobQueue, done) producer(jobQueue, done) time.Sleep(1 * time.Second) } ================================================ FILE: examples/local-deadlock/main.go ================================================ package main import ( "fmt" "time" ) func Work() { for { fmt.Println("Working") time.Sleep(1 * time.Second) } } func Send(ch chan<- int) { ch <- 42 } func Recv(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch, done := make(chan int), make(chan int) go Send(ch) go Recv(ch, done) go Recv(ch, done) go Work() <-done <-done } ================================================ FILE: examples/local-deadlock-fixed/main.go ================================================ package main import ( "fmt" "time" ) func Work() { for { fmt.Println("Working") time.Sleep(1 * time.Second) } } func Send(ch chan<- int) { ch <- 42 } func Recv(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch, done := make(chan int), make(chan int) go Send(ch) go Recv(ch, done) go Work() <-done } ================================================ FILE: examples/loop-variations/main.go ================================================ package main // This example test different loop and is used for checking loop SSA // generation. import "fmt" func main() { /* xs := []int{1, 2, 3} for _, s := range xs { fmt.Println(s) } xs2 := [3]int{1, 2, 3} for _, s := range xs2 { fmt.Println(s) } for i := 0; i < 3; i++ { fmt.Println("xs[i]", xs[i]) } for { fmt.Println("looooopppp one") // This executes once break } loopcond := func(i int) bool { return false } for k := 0; loopcond(k); k++ { fmt.Println("Loop k: ", k) } */ for i := 0; i < 3; i++ { for j := 0; j < 2; j++ { fmt.Printf("Index (%d, %d) ", i, j) x := make(chan int) <-x } fmt.Printf("ASBCD") } /* x := []int{1, 2, 3, 4} for i := range x { // Range loop (safe) fmt.Println(i) } ch := make(chan int) go func(ch chan int) { ch <- 42; close(ch) }(ch) for v := range ch { fmt.Println(v) } for { fmt.Println("Infinite looooopppp") } */ } ================================================ FILE: examples/makechan-in-loop/main.go ================================================ // The loop-chan example shows what will happen when a channel is created in a // loop (and stored in slices). Pointer analysis identifies the channel // operations with channels created inside the loop, but the loop indices are // ignored and the analysis must make an assumption that the channels inside the // slices are accessed in order. package main func main() { chans := make([]chan int, 5) for i := range chans { chans[i] = make(chan int, 1) } for _, ch := range chans { ch <- 42 } for _, ch := range chans { <-ch } } ================================================ FILE: examples/md5/main.go ================================================ // +build OMIT package main import ( "crypto/md5" "errors" "fmt" "io/ioutil" "os" "path/filepath" "sort" "sync" ) // A result is the product of reading and summing a file using MD5. type result struct { path string sum [md5.Size]byte err error } // sumFiles starts goroutines to walk the directory tree at root and digest each // regular file. These goroutines send the results of the digests on the result // channel and send the result of the walk on the error channel. If done is // closed, sumFiles abandons its work. func sumFiles(done <-chan struct{}, root string) (<-chan result, <-chan error) { // For each regular file, start a goroutine that sums the file and sends // the result on c. Send the result of the walk on errc. c := make(chan result) errc := make(chan error, 1) go func() { // HL var wg sync.WaitGroup err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.Mode().IsRegular() { return nil } wg.Add(1) go func() { // HL data, err := ioutil.ReadFile(path) select { case c <- result{path, md5.Sum(data), err}: // HL case <-done: // HL } wg.Done() }() // Abort the walk if done is closed. select { case <-done: // HL return errors.New("walk canceled") default: return nil } }) // Walk has returned, so all calls to wg.Add are done. Start a // goroutine to close c once all the sends are done. go func() { // HL wg.Wait() close(c) // HL }() // No select needed here, since errc is buffered. errc <- err // HL }() return c, errc } // MD5All reads all the files in the file tree rooted at root and returns a map // from file path to the MD5 sum of the file's contents. If the directory walk // fails or any read operation fails, MD5All returns an error. In that case, // MD5All does not wait for inflight read operations to complete. func MD5All(root string) (map[string][md5.Size]byte, error) { // MD5All closes the done channel when it returns; it may do so before // receiving all the values from c and errc. done := make(chan struct{}) // HLdone defer close(done) // HLdone c, errc := sumFiles(done, root) // HLdone m := make(map[string][md5.Size]byte) for r := range c { // HLrange if r.err != nil { return nil, r.err } m[r.path] = r.sum } if err := <-errc; err != nil { return nil, err } return m, nil } func main() { // Calculate the MD5 sum of all files under the specified directory, // then print the results sorted by path name. m, err := MD5All(os.Args[1]) if err != nil { fmt.Println(err) return } var paths []string for path := range m { paths = append(paths, path) } sort.Strings(paths) for _, path := range paths { fmt.Printf("%x %s\n", m[path], path) } } ================================================ FILE: examples/multi-makechan-same-var/main.go ================================================ // createchan is an example which reuses a channel for different operations in // an expanded form. package main func createChan() chan int { return make(chan int, 1) } func main() { ch := createChan() ch <- 42 ch = createChan() ch <- 3 } ================================================ FILE: examples/multiple-files/main.go ================================================ package main import ( "fmt" ) func main() { fmt.Println("Do stuff") x() fmt.Println("Do more stuff") } ================================================ FILE: examples/multiple-files/x.go ================================================ package main import "fmt" func x() { fmt.Println("Hello X") } ================================================ FILE: examples/multiple-timeout/main.go ================================================ // Command multiple-timeout is an example which uses multiple branches of // time.After. // // The main purpose is to test if the extracted local type can distinguish // between two "externally created" channels (in time.After), and are // initialised separately in the local graph. package main import "time" func main() { ch := make(chan int, 1) go func(ch chan int) { time.Sleep(10 * time.Second); ch <- 42 }(ch) select { case <-ch: case <-time.After(2 * time.Second): case <-time.After(4 * time.Second): } } ================================================ FILE: examples/parallel-buffered-recursive-fibonacci/main.go ================================================ // Command parallel-recursive-fibonacci is a recursive fibonacci which spawns a // new goroutine per fib call. package main import "fmt" func main() { ch := make(chan int) go fib(10, ch) fmt.Println(<-ch) } func fib(n int, ch chan<- int) { if n <= 1 { ch <- n return } ch1 := make(chan int, 2) go fib(n-1, ch1) go fib(n-2, ch1) ch <- <-ch1 + <-ch1 } ================================================ FILE: examples/parallel-recursive-fibonacci/main.go ================================================ // Command parallel-recursive-fibonacci is a recursive fibonacci which spawns a // new goroutine per fib call. package main import "fmt" func main() { ch := make(chan int) go fib(10, ch) fmt.Println(<-ch) } func fib(n int, ch chan<- int) { if n <= 1 { ch <- n return } ch1 := make(chan int) ch2 := make(chan int) go fib(n-1, ch1) go fib(n-2, ch2) ch <- <-ch1 + <-ch2 } ================================================ FILE: examples/parallel-twoprocess-fibonacci/main.go ================================================ // Command parallel-twoprocess-fibonacci is an improved version of parallel // fibonacci which limits to only spawning 2 goroutines. package main import "fmt" func fib(n int) int { if n <= 1 { return n } return fib(n-1) + fib(n-2) } func fibParallel(n int, ch chan<- int) { ch <- fib(n) } func main() { ch1 := make(chan int) ch2 := make(chan int) n := 10 go fibParallel(n-1, ch1) go fibParallel(n-2, ch2) fmt.Println(<-ch1 + <-ch2) } ================================================ FILE: examples/philo/main.go ================================================ package main // philio test case from Stadtmuller, Thieman import ( "fmt" ) func philo(id int, forks chan int) { for { <-forks <-forks fmt.Printf("%d eats\n", id) forks <- 1 forks <- 1 } } func main() { forks := make(chan int) go func() { forks <- 1 }() go func() { forks <- 1 }() go func() { forks <- 1 }() go philo(1, forks) go philo(2, forks) philo(3, forks) } ================================================ FILE: examples/popl17/alt-bit/main.go ================================================ package main // Alternating bit - from Milner's Communication and Concurrency import ( "fmt" ) func main() { trans := make(chan int, 1) ack := make(chan int, 1) go tx(trans, ack) rx(ack, trans) } func tx(snd chan<- int, ack <-chan int) { b := 0 for { fmt.Printf("tx[%d]: accept\n", b) fmt.Printf("tx[%d]: send[%d]\n", b, b) snd <- b SENDING: for { // SENDING[b] select { case x := <-ack: if x == b { fmt.Printf("tx[%d]: ack[b]\n", b) b = (b + 1) % 2 break SENDING // ACCEPT !b } else { fmt.Printf("tx[%d]: ack[!b]\n", b) b = (b + 1) % 2 // SENDING b } case snd <- b: fmt.Printf("tx[%d]: timeout\n", b) fmt.Printf("tx[%d]: send[%d]\n", b, b) // SENDING b } } } } func rx(reply chan<- int, trans <-chan int) { b := 1 for { fmt.Printf("rx[%d]: deliver\n", b) fmt.Printf("rx[%d]: reply[%d]\n", b, b) reply <- b REPLYING: for { select { // REPLYING[b] case x := <-trans: if x != b { fmt.Printf("rx[%d]: trans[!b]\n", b) break REPLYING // DELIVER !b } else { fmt.Printf("rx[%d]: trans[b]\n", b) // REPLYING b } case reply <- b: fmt.Printf("rx[%d]: timeout\n", b) fmt.Printf("rx[%d]: reply[%d]\n", b, b) // REPLYING b } } } } ================================================ FILE: examples/popl17/concsys/main.go ================================================ package main import ( "fmt" "math/rand" "time" ) type Result string type Search func(query string) Result func fakeSearch(kind string) Search { return func(query string) Result { time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) return Result(fmt.Sprintf("%s result for %q\n", kind, query)) } } var ( Web = fakeSearch("web") Web1 = fakeSearch("web-1") Web2 = fakeSearch("web-2") Image = fakeSearch("image") Image1 = fakeSearch("image-1") Image2 = fakeSearch("image-2") Image3 = fakeSearch("image-3") Video = fakeSearch("video") Video1 = fakeSearch("video-1") Video2 = fakeSearch("video-2") Video3 = fakeSearch("video-3") ) func SequentialSearch(query string) (results []Result) { results = append(results, Web(query)) results = append(results, Image(query)) results = append(results, Video(query)) return } func ConcurrentSearch(query string) (results []Result) { c := make(chan Result) go func(c chan Result) { c <- Web(query) }(c) go func(c chan Result) { c <- Image(query) }(c) go func(c chan Result) { c <- Video(query) }(c) for i := 0; i < 3; i++ { result := <-c results = append(results, result) } return } func ConcurrentSearchWithCutOff(query string) (results []Result) { c := make(chan Result) go func() { c <- Web(query) }() go func() { c <- Image(query) }() go func() { c <- Video(query) }() timeout := time.After(80 * time.Millisecond) for i := 0; i < 3; i++ { // for each goroutine that is ready pick up results select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("timed out") return } } return } func First(query string, replicas ...Search) Result { c := make(chan Result, 1) searchReplica := func(i int) { c <- replicas[i](query) } for i := range replicas { searchReplica(i) } return <-c } func ReplicaSearch(query string) (results []Result) { c := make(chan Result) go func(c chan Result) { c <- Web1(query) }(c) go func(c chan Result) { c <- Image1(query) }(c) go func(c chan Result) { c <- Video1(query) }(c) timeout := time.After(80 * time.Millisecond) //for i := 0; i < 3; i++ { select { case result := <-c: results = append(results, result) case <-timeout: fmt.Println("seach timed out") return } //} return } func main() { rand.Seed(time.Now().UnixNano()) start := time.Now() results := SequentialSearch("golang") //results := ConcurrentSearch("golang") // results := ConcurrentSearchWithCutOff("golang") // results := First("golang", fakeSearch("replica-1"), fakeSearch("replica-2")) results = ReplicaSearch("golang") elapsed := time.Since(start) fmt.Println(results) fmt.Println(elapsed) } ================================================ FILE: examples/popl17/cond-recur/main.go ================================================ // Command conditional-recur has a recursion with conditional on one goroutine // and another receiving until a done message is received. package main import "fmt" func x(ch chan int, done chan struct{}) { i := 0 for { if i < 3 { ch <- i fmt.Println("Sent", i) i++ } else { done <- struct{}{} return } } } func main() { done := make(chan struct{}) ch := make(chan int) go x(ch, done) FINISH: for { select { case x := <-ch: fmt.Println(x) case <-done: break FINISH } } } ================================================ FILE: examples/popl17/dinephil/main.go ================================================ package main // Example from CONCUR 14 paper by Giachino et al. // doi: 10.1007/978-3-662-44584-6_6 import ( "fmt" "time" ) func Fork(fork chan int) { for { fork <- 1 <-fork } } // philosophers (infinite recursive). func phil(fork1, fork2 chan int, id int) { var x1, x2 int for { select { case x1 = <-fork1: select { case x2 = <-fork2: fmt.Printf("phil %d got both fork\n", id) fork1 <- x1 fork2 <- x2 default: fork1 <- x1 } case x1 = <-fork2: select { case x2 = <-fork1: fmt.Printf("phil %d got both fork\n", id) fork2 <- x1 fork1 <- x2 default: fork2 <- x1 } } } } func main() { fork1 := make(chan int) fork2 := make(chan int) fork3 := make(chan int) go phil(fork1, fork2, 0) // deadlock if phil(fork2, fork1, 0) go phil(fork2, fork3, 1) go phil(fork3, fork1, 2) go Fork(fork1) go Fork(fork2) go Fork(fork3) time.Sleep(10 * time.Second) } ================================================ FILE: examples/popl17/fact/main.go ================================================ package main import "fmt" func main() { ch := make(chan int) go fact(5, ch) fmt.Println(<-ch) } func fact(n int, results chan<- int) { if n <= 1 { results <- n return } ch := make(chan int) go fact(n-1, ch) results <- n * <-ch } ================================================ FILE: examples/popl17/fanin/main.go ================================================ package main import ( "fmt" ) func work1(out chan<- int) { for { out <- 42 } } func work2(out chan<- int) { for { out <- 42 } } func fanin(ch1, ch2, c chan int) { go func(ch1, ch2, c chan int) { for { select { case s := <-ch1: c <- s case s := <-ch2: c <- s } } }(ch1, ch2, c) for { fmt.Println(<-c) } } func main() { input1 := make(chan int) input2 := make(chan int) go work1(input1) go work2(input2) c := make(chan int) fanin(input1, input2, c) } ================================================ FILE: examples/popl17/fanin-alt/main.go ================================================ package main // fanin pattern, using for-range loop to consume values (syntactic sugar of // loop over r, ok := <-ch) import ( "fmt" "time" ) func work(out chan<- int) { for { out <- 42 } } func fanin(ch1, ch2, c chan int) { go func(ch1, ch2, c chan int) { for { select { case s := <-ch1: c <- s case s := <-ch2: c <- s default: close(c) return } } }(ch1, ch2, c) for v := range c { fmt.Println(v) } } func main() { input1 := make(chan int) input2 := make(chan int) go work(input1) go work(input2) c := make(chan int) fanin(input1, input2, c) time.Sleep(1 * time.Second) } ================================================ FILE: examples/popl17/fib/main.go ================================================ // Command parallel-recursive-fibonacci is a recursive fibonacci which spawns a // new goroutine per fib call. package main import "fmt" func main() { ch := make(chan int) go fib(10, ch) fmt.Println(<-ch) } func fib(n int, ch chan<- int) { if n <= 1 { ch <- n return } ch1 := make(chan int) ch2 := make(chan int) go fib(n-1, ch1) go fib(n-2, ch2) ch <- <-ch1 + <-ch2 } ================================================ FILE: examples/popl17/fib-async/main.go ================================================ // Command parallel-recursive-fibonacci is a recursive fibonacci which spawns a // new goroutine per fib call. package main import "fmt" func main() { ch := make(chan int) go fib(10, ch) fmt.Println(<-ch) } func fib(n int, ch chan<- int) { if n <= 1 { ch <- n return } ch1 := make(chan int, 2) go fib(n-1, ch1) go fib(n-2, ch1) ch <- <-ch1 + <-ch1 } ================================================ FILE: examples/popl17/fixed/main.go ================================================ package main import ( "fmt" "time" ) func Work() { for { fmt.Println("Working") time.Sleep(1 * time.Second) } } func Send(ch chan<- int) { ch <- 42 } func Recv(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch, done := make(chan int), make(chan int) go Send(ch) go Recv(ch, done) go Work() <-done } ================================================ FILE: examples/popl17/forselect/main.go ================================================ // Command nodet-for-select is a for-select pattern between two compatible // recursive select. package main import "fmt" func sel1(ch1, ch2 chan int, done chan struct{}) { for { select { case <-ch1: fmt.Println("sel1: recv") done <- struct{}{} return case ch2 <- 1: fmt.Println("sel1: send") } } } func sel2(ch1, ch2 chan int, done chan struct{}) { for { select { case <-ch2: fmt.Println("sel2: recv") case ch1 <- 2: fmt.Println("sel2: send") done <- struct{}{} return } } } func main() { done := make(chan struct{}) a := make(chan int) b := make(chan int) go sel1(a, b, done) go sel2(a, b, done) <-done <-done } ================================================ FILE: examples/popl17/jobsched/main.go ================================================ package main import ( "fmt" "time" ) var i int func worker(id int, jobQueue <-chan int, done <-chan struct{}) { for { select { case jobID := <-jobQueue: fmt.Println(id, "Executing job", jobID) case <-done: fmt.Println(id, "Quits") return } } } func morejob() bool { i++ return i < 20 } func producer(q chan int, done chan struct{}) { for morejob() { q <- 42 } close(done) } func main() { jobQueue := make(chan int) done := make(chan struct{}) go worker(1, jobQueue, done) go worker(2, jobQueue, done) producer(jobQueue, done) time.Sleep(1 * time.Second) } ================================================ FILE: examples/popl17/mismatch/main.go ================================================ package main import ( "fmt" "time" ) func Work() { for { fmt.Println("Working") time.Sleep(1 * time.Second) } } func Send(ch chan<- int) { ch <- 42 } func Recv(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch, done := make(chan int), make(chan int) go Send(ch) go Recv(ch, done) go Recv(ch, done) go Work() <-done <-done } ================================================ FILE: examples/popl17/sieve/main.go ================================================ // Command infinite-primesieve is a modified primesieve example from Golang // blog. The program generates an infinite list (instead of a fixed number) of // prime numbers. // // Original: https://golang.org/doc/play/sieve.go // package main import ( "fmt" ) // Send the sequence 2, 3, 4, ... to channel 'ch'. func Generate(ch chan<- int) { for i := 2; ; i++ { ch <- i // Send 'i' to channel 'ch'. } } // Copy the values from channel 'in' to channel 'out', // removing those divisible by 'prime'. func Filter(in <-chan int, out chan<- int, prime int) { for { i := <-in // Receive value from 'in'. if i%prime != 0 { out <- i // Send 'i' to 'out'. } } } // The prime sieve: Daisy-chain Filter processes. func main() { ch := make(chan int) // Create a new channel. go Generate(ch) // Launch Generate goroutine. for i := 0; ; i++ { prime := <-ch fmt.Println(prime) ch1 := make(chan int) go Filter(ch, ch1, prime) ch = ch1 } } ================================================ FILE: examples/popl17ae/close/main.go ================================================ package main func main() { ch := make(chan int) close(ch) } ================================================ FILE: examples/popl17ae/emptyselect/main.go ================================================ package main func s(ch chan int) { ch <- 5 } func main() { ch := make(chan int, 2) select { case <-ch: default: } s(ch) } ================================================ FILE: examples/powsers/powser1.go ================================================ // run // +build ignore // Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Test concurrency primitives: power series. // Power series package // A power series is a channel, along which flow rational // coefficients. A denominator of zero signifies the end. // Original code in Newsqueak by Doug McIlroy. // See Squinting at Power Series by Doug McIlroy, // http://www.cs.bell-labs.com/who/rsc/thread/squint.pdf package main import "os" type rat struct { num, den int64 // numerator, denominator } func (u rat) pr() { if u.den == 1 { print(u.num) } else { print(u.num, "/", u.den) } print(" ") } func (u rat) eq(c rat) bool { return u.num == c.num && u.den == c.den } type dch struct { req chan int dat chan rat nam int } type dch2 [2]*dch var chnames string var chnameserial int var seqno int func mkdch() *dch { c := chnameserial % len(chnames) chnameserial++ d := new(dch) d.req = make(chan int) d.dat = make(chan rat) d.nam = c return d } func mkdch2() *dch2 { d2 := new(dch2) d2[0] = mkdch() d2[1] = mkdch() return d2 } // split reads a single demand channel and replicates its // output onto two, which may be read at different rates. // A process is created at first demand for a rat and dies // after the rat has been sent to both outputs. // When multiple generations of split exist, the newest // will service requests on one channel, which is // always renamed to be out[0]; the oldest will service // requests on the other channel, out[1]. All generations but the // newest hold queued data that has already been sent to // out[0]. When data has finally been sent to out[1], // a signal on the release-wait channel tells the next newer // generation to begin servicing out[1]. func dosplit(in *dch, out *dch2, wait chan int) { both := false // do not service both channels select { case <-out[0].req: case <-wait: both = true select { case <-out[0].req: case <-out[1].req: out[0], out[1] = out[1], out[0] } } seqno++ in.req <- seqno release := make(chan int) go dosplit(in, out, release) dat := <-in.dat out[0].dat <- dat if !both { <-wait } <-out[1].req out[1].dat <- dat release <- 0 } func split(in *dch, out *dch2) { release := make(chan int) go dosplit(in, out, release) release <- 0 } func put(dat rat, out *dch) { <-out.req out.dat <- dat } func get(in *dch) rat { seqno++ in.req <- seqno return <-in.dat } // Get one rat from each of n demand channels func getn(in []*dch) []rat { n := len(in) if n != 2 { panic("bad n in getn") } req := new([2]chan int) dat := new([2]chan rat) out := make([]rat, 2) var i int var it rat for i = 0; i < n; i++ { req[i] = in[i].req dat[i] = nil } for n = 2 * n; n > 0; n-- { seqno++ select { case req[0] <- seqno: dat[0] = in[0].dat req[0] = nil case req[1] <- seqno: dat[1] = in[1].dat req[1] = nil case it = <-dat[0]: out[0] = it dat[0] = nil case it = <-dat[1]: out[1] = it dat[1] = nil } } return out } // Get one rat from each of 2 demand channels func get2(in0 *dch, in1 *dch) []rat { return getn([]*dch{in0, in1}) } func copy(in *dch, out *dch) { for { <-out.req out.dat <- get(in) } } func repeat(dat rat, out *dch) { for { put(dat, out) } } type PS *dch // power series type PS2 *[2]PS // pair of power series var Ones PS var Twos PS func mkPS() *dch { return mkdch() } func mkPS2() *dch2 { return mkdch2() } // Conventions // Upper-case for power series. // Lower-case for rationals. // Input variables: U,V,... // Output variables: ...,Y,Z // Integer gcd; needed for rational arithmetic func gcd(u, v int64) int64 { if u < 0 { return gcd(-u, v) } if u == 0 { return v } return gcd(v%u, u) } // Make a rational from two ints and from one int func i2tor(u, v int64) rat { g := gcd(u, v) var r rat if v > 0 { r.num = u / g r.den = v / g } else { r.num = -u / g r.den = -v / g } return r } func itor(u int64) rat { return i2tor(u, 1) } var zero rat var one rat // End mark and end test var finis rat func end(u rat) int64 { if u.den == 0 { return 1 } return 0 } // Operations on rationals func add(u, v rat) rat { g := gcd(u.den, v.den) return i2tor(u.num*(v.den/g)+v.num*(u.den/g), u.den*(v.den/g)) } func mul(u, v rat) rat { g1 := gcd(u.num, v.den) g2 := gcd(u.den, v.num) var r rat r.num = (u.num / g1) * (v.num / g2) r.den = (u.den / g2) * (v.den / g1) return r } func neg(u rat) rat { return i2tor(-u.num, u.den) } func sub(u, v rat) rat { return add(u, neg(v)) } func inv(u rat) rat { // invert a rat if u.num == 0 { panic("zero divide in inv") } return i2tor(u.den, u.num) } // print eval in floating point of PS at x=c to n terms func evaln(c rat, U PS, n int) { xn := float64(1) x := float64(c.num) / float64(c.den) val := float64(0) for i := 0; i < n; i++ { u := get(U) if end(u) != 0 { break } val = val + x*float64(u.num)/float64(u.den) xn = xn * x } print(val, "\n") } // Print n terms of a power series func printn(U PS, n int) { done := false for ; !done && n > 0; n-- { u := get(U) if end(u) != 0 { done = true } else { u.pr() } } print(("\n")) } // Evaluate n terms of power series U at x=c func eval(c rat, U PS, n int) rat { if n == 0 { return zero } y := get(U) if end(y) != 0 { return zero } return add(y, mul(c, eval(c, U, n-1))) } // Power-series constructors return channels on which power // series flow. They start an encapsulated generator that // puts the terms of the series on the channel. // Make a pair of power series identical to a given power series func Split(U PS) *dch2 { UU := mkdch2() go split(U, UU) return UU } // Add two power series func Add(U, V PS) PS { Z := mkPS() go func() { var uv []rat for { <-Z.req uv = get2(U, V) switch end(uv[0]) + 2*end(uv[1]) { case 0: Z.dat <- add(uv[0], uv[1]) case 1: Z.dat <- uv[1] copy(V, Z) case 2: Z.dat <- uv[0] copy(U, Z) case 3: Z.dat <- finis } } }() return Z } // Multiply a power series by a constant func Cmul(c rat, U PS) PS { Z := mkPS() go func() { done := false for !done { <-Z.req u := get(U) if end(u) != 0 { done = true } else { Z.dat <- mul(c, u) } } Z.dat <- finis }() return Z } // Subtract func Sub(U, V PS) PS { return Add(U, Cmul(neg(one), V)) } // Multiply a power series by the monomial x^n func Monmul(U PS, n int) PS { Z := mkPS() go func() { for ; n > 0; n-- { put(zero, Z) } copy(U, Z) }() return Z } // Multiply by x func Xmul(U PS) PS { return Monmul(U, 1) } func Rep(c rat) PS { Z := mkPS() go repeat(c, Z) return Z } // Monomial c*x^n func Mon(c rat, n int) PS { Z := mkPS() go func() { if c.num != 0 { for ; n > 0; n = n - 1 { put(zero, Z) } put(c, Z) } put(finis, Z) }() return Z } func Shift(c rat, U PS) PS { Z := mkPS() go func() { put(c, Z) copy(U, Z) }() return Z } // simple pole at 1: 1/(1-x) = 1 1 1 1 1 ... // Convert array of coefficients, constant term first // to a (finite) power series /* func Poly(a []rat) PS { Z:=mkPS() begin func(a []rat, Z PS) { j:=0 done:=0 for j=len(a); !done&&j>0; j=j-1) if(a[j-1].num!=0) done=1 i:=0 for(; i 1 { // print print("Ones: ") printn(Ones, 10) print("Twos: ") printn(Twos, 10) print("Add: ") printn(Add(Ones, Twos), 10) print("Diff: ") printn(Diff(Ones), 10) print("Integ: ") printn(Integ(zero, Ones), 10) print("CMul: ") printn(Cmul(neg(one), Ones), 10) print("Sub: ") printn(Sub(Ones, Twos), 10) print("Mul: ") printn(Mul(Ones, Ones), 10) print("Exp: ") printn(Exp(Ones), 15) print("MonSubst: ") printn(MonSubst(Ones, neg(one), 2), 10) print("ATan: ") printn(Integ(zero, MonSubst(Ones, neg(one), 2)), 10) } else { // test check(Ones, one, 5, "Ones") check(Add(Ones, Ones), itor(2), 0, "Add Ones Ones") // 1 1 1 1 1 check(Add(Ones, Twos), itor(3), 0, "Add Ones Twos") // 3 3 3 3 3 a := make([]rat, N) d := Diff(Ones) for i := 0; i < N; i++ { a[i] = itor(int64(i + 1)) } checka(d, a, "Diff") // 1 2 3 4 5 in := Integ(zero, Ones) a[0] = zero // integration constant for i := 1; i < N; i++ { a[i] = i2tor(1, int64(i)) } checka(in, a, "Integ") // 0 1 1/2 1/3 1/4 1/5 check(Cmul(neg(one), Twos), itor(-2), 10, "CMul") // -1 -1 -1 -1 -1 check(Sub(Ones, Twos), itor(-1), 0, "Sub Ones Twos") // -1 -1 -1 -1 -1 m := Mul(Ones, Ones) for i := 0; i < N; i++ { a[i] = itor(int64(i + 1)) } checka(m, a, "Mul") // 1 2 3 4 5 e := Exp(Ones) a[0] = itor(1) a[1] = itor(1) a[2] = i2tor(3, 2) a[3] = i2tor(13, 6) a[4] = i2tor(73, 24) a[5] = i2tor(167, 40) a[6] = i2tor(4051, 720) a[7] = i2tor(37633, 5040) a[8] = i2tor(43817, 4480) a[9] = i2tor(4596553, 362880) checka(e, a, "Exp") // 1 1 3/2 13/6 73/24 at := Integ(zero, MonSubst(Ones, neg(one), 2)) for c, i := 1, 0; i < N; i++ { if i%2 == 0 { a[i] = zero } else { a[i] = i2tor(int64(c), int64(i)) c *= -1 } } checka(at, a, "ATan") // 0 -1 0 -1/3 0 -1/5 /* t := Revert(Integ(zero, MonSubst(Ones, neg(one), 2))) a[0] = zero a[1] = itor(1) a[2] = zero a[3] = i2tor(1,3) a[4] = zero a[5] = i2tor(2,15) a[6] = zero a[7] = i2tor(17,315) a[8] = zero a[9] = i2tor(62,2835) checka(t, a, "Tan") // 0 1 0 1/3 0 2/15 */ } } ================================================ FILE: examples/producer-consumer/main.go ================================================ package main // Producer-Consumer example. // http://www.golangpatterns.info/concurrency/producer-consumer import "fmt" var done = make(chan bool) var msgs = make(chan int) func produce() { for i := 0; i < 10; i++ { msgs <- i } done <- true } func consume() { for { msg := <-msgs fmt.Println(msg) } } func main() { go produce() go consume() <-done } ================================================ FILE: examples/ring-pattern/main.go ================================================ package main import "fmt" func numprocs() int { return 10 } func adder(in <-chan int, out chan<- int) { for { out <- (<-in + 1) } } func main() { chOne := make(chan int) chOut := chOne chIn := chOne for i := 0; i < numprocs(); i++ { chOut = make(chan int) go adder(chIn, chOut) chIn = chOut } chOne <- 0 fmt.Println(<-chOut) } ================================================ FILE: examples/russ-cox-fizzbuzz/main.go ================================================ package main import "fmt" func main() { c := generate() c = filter(c, 3, "Fizz") c = filter(c, 5, "Buzz") for i := 1; i <= 100; i++ { if s := <-c; s != "" { fmt.Println(s) } else { fmt.Println(i) } } } func generate() <-chan string { c := make(chan string) go func() { for { c <- "" } }() return c } func filter(c <-chan string, n int, label string) <-chan string { out := make(chan string) go func() { for { for i := 0; i < n-1; i++ { out <- <-c } out <- <-c + label } }() return out } ================================================ FILE: examples/select-with-continuation/main.go ================================================ package main import ( "fmt" ) func main() { ch1 := make(chan int) ch2 := make(chan int) ch3 := make(chan int) select { case x := <-ch1: fmt.Println("Received x", x) case ch2 <- 43: fmt.Println("ok sent") case <-ch3: default: fmt.Println("asdfsdafsad") } ch1 <- 32 fmt.Println("asdfsadfs") } ================================================ FILE: examples/select-with-weak-mismatch/main.go ================================================ package main // This example tests how select works. Note that ch1 is never selected. import ( "fmt" ) func main() { ch0 := make(chan int) ch1 := make(chan int) go func() { ch0 <- 42 }() // Blocking select { case x := <-ch0: fmt.Printf("Result is %d\n", x) case ch1 <- 2: // This is a mismatch, no receive on ch1 } } ================================================ FILE: examples/semaphores/main.go ================================================ package main // Semaphores. // Emulating semaphores with buffered channels. // http://www.golangpatterns.info/concurrency/semaphores type empty struct{} type Semaphore chan empty func NewSemaphore(cap int) Semaphore { return make(Semaphore, cap) } // acquire n resources func (s Semaphore) P(n int) { e := empty{} for i := 0; i < n; i++ { s <- e } } // release n resources func (s Semaphore) V(n int) { for i := 0; i < n; i++ { <-s } } func main() { } ================================================ FILE: examples/send-recv-with-interfaces/main.go ================================================ package main import ( "time" ) type Interacter interface { Send(ch chan int) Recv(ch chan int) } type S struct{} func (st S) Send(ch chan int) { ch <- 42 } func (st S) Recv(ch chan int) { <-ch } func main() { x := S{} c := make(chan int) go x.Send(c) x.Recv(c) time.Sleep(1 * time.Second) } ================================================ FILE: examples/simple/main.go ================================================ package main // Simplest way of disabling the deadlock detector. import _ "net" func main() { ch := make(chan int) <-ch } ================================================ FILE: examples/single-gortn-method-call/main.go ================================================ package main var x = 1323 type T struct { x int y chan int } func (t *T) setup(y int) { t.x = y t.y = make(chan int) } func main() { var t T x := 12 t.setup(x) t.y <- 42 // Nobody to receive! } ================================================ FILE: examples/spawn-in-choice/main.go ================================================ package main import ( "flag" "fmt" "os" "strconv" "time" ) func S(out chan int) { out <- 42 fmt.Println("Sent 42") } func R(in chan int) { fmt.Printf("Received %d\n", <-in) } // Natural branch func main() { ch1 := make(chan int) ch2 := make(chan int) flag.Parse() fmt.Printf("NArg=%d\n", flag.NArg()) if flag.NArg() > 0 { s := flag.Arg(0) i, err := strconv.Atoi(s) if err != nil { os.Exit(2) } if i > 0 { fmt.Println("Branch one") go R(ch2) go S(ch2) } else { fmt.Println("Branch two") go R(ch1) go S(ch1) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/squaring-cancellation/main.go ================================================ // Command squaring-cancellation comes from Golang blog to demonstrate fan-in and // explicit cancellation. This version is with explicit cancel (through done). // // Source: https://blog.golang.org/pipelines package main import ( "fmt" "sync" ) func gen(done <-chan struct{}, nums ...int) <-chan int { out := make(chan int) go func() { defer close(out) for _, n := range nums { select { case out <- n: case <-done: return } } }() return out } func sq(done <-chan struct{}, in <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for n := range in { select { case out <- n * n: case <-done: return } } }() return out } func merge(done <-chan struct{}, cs ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) // Start an output goroutine for each input channel in cs. output // copies values from c to out until c is closed, then calls wg.Done. output := func(c <-chan int) { defer wg.Done() for n := range c { select { case out <- n: case <-done: return } } } wg.Add(len(cs)) for _, c := range cs { go output(c) } // Start a goroutine to close out once all the output goroutines are // done. This must start after the wg.Add call. go func() { wg.Wait() close(out) }() return out } func main() { // Set up a done channel that's shared by the whole pipeline, // and close that channel when this pipeline exits, as a signal // for all the goroutines we started to exit. done := make(chan struct{}) defer close(done) in := gen(done, 2, 3) // Distribute the sq work across two goroutines that both read from in. c1 := sq(done, in) c2 := sq(done, in) // Consume the first value from c1 and c2. out := merge(done, c1, c2) fmt.Println(<-out) // done will be closed by the deferred call. } ================================================ FILE: examples/squaring-fanin/main.go ================================================ // Command squaring-fainin comes from Golang blog to demonstrate fan-in and // explicit cancellation. This version uses fan-in to read all produced value. // // Source: https://blog.golang.org/pipelines package main import ( "fmt" "sync" ) // The first stage, gen, is a function that converts a list of integers to a // channel that emits the integers in the list. The gen function starts a // goroutine that sends the integers on the channel and closes the channel when // all the values have been sent: func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out } // The second stage, sq, receives integers from a channel and returns a channel // that emits the square of each received integer. After the inbound channel is // closed and this stage has sent all the values downstream, it closes the // outbound channel: func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out } func merge(cs ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) // Start an output goroutine for each input channel in cs. output // copies values from c to out until c is closed, then calls wg.Done. output := func(c <-chan int) { for n := range c { out <- n } wg.Done() } wg.Add(len(cs)) for _, c := range cs { go output(c) } // Start a goroutine to close out once all the output goroutines are // done. This must start after the wg.Add call. go func() { wg.Wait() close(out) }() return out } func main() { in := gen(2, 3) // Distribute the sq work across two goroutines that both read from in. c1 := sq(in) c2 := sq(in) // Consume the merged output from c1 and c2. for n := range merge(c1, c2) { fmt.Println(n) // 4 then 9, or 9 then 4 } } ================================================ FILE: examples/squaring-fanin-bad/main.go ================================================ // Command squaring-fanin-bad comes from Golang blog to demonstrate fan-in and // explicit cancellation. This version uses fan-in but not all values are // consumed (resources leak). // // Source: https://blog.golang.org/pipelines package main import ( "fmt" "sync" ) // The first stage, gen, is a function that converts a list of integers to a // channel that emits the integers in the list. The gen function starts a // goroutine that sends the integers on the channel and closes the channel when // all the values have been sent: func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out } // The second stage, sq, receives integers from a channel and returns a channel // that emits the square of each received integer. After the inbound channel is // closed and this stage has sent all the values downstream, it closes the // outbound channel: func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out } func merge(cs ...<-chan int) <-chan int { var wg sync.WaitGroup out := make(chan int) // Start an output goroutine for each input channel in cs. output // copies values from c to out until c is closed, then calls wg.Done. output := func(c <-chan int) { for n := range c { out <- n } wg.Done() } wg.Add(len(cs)) for _, c := range cs { go output(c) } // Start a goroutine to close out once all the output goroutines are // done. This must start after the wg.Add call. go func() { wg.Wait() close(out) }() return out } func main() { in := gen(2, 3) // Distribute the sq work across two goroutines that both read from in. c1 := sq(in) c2 := sq(in) // Consume the first value from output out := merge(c1, c2) fmt.Println(<-out) return // Since we didn't receive the second value from out, // one of the output goroutines is hung attempting to send it. } ================================================ FILE: examples/squaring-pipeline/main.go ================================================ // Command squaring-pipeline comes from Golang blog to demonstrate fan-in and // explicit cancellation. This is a modified example of the example. // // Source: https://blog.golang.org/pipelines package main import "fmt" // The first stage, gen, is a function that converts a list of integers to a // channel that emits the integers in the list. The gen function starts a // goroutine that sends the integers on the channel and closes the channel when // all the values have been sent: func gen(nums ...int) <-chan int { out := make(chan int) go func() { for _, n := range nums { out <- n } close(out) }() return out } // The second stage, sq, receives integers from a channel and returns a channel // that emits the square of each received integer. After the inbound channel is // closed and this stage has sent all the values downstream, it closes the // outbound channel: func sq(in <-chan int) <-chan int { out := make(chan int) go func() { for n := range in { out <- n * n } close(out) }() return out } // The main function sets up the pipeline and runs the final stage: it receives // values from the second stage and prints each one, until the channel is // closed: func main() { // Set up the pipeline and consume the output. for n := range sq(sq(gen(2, 3))) { fmt.Println(n) // 15 then 81 } } ================================================ FILE: examples/struct-done-channel/main.go ================================================ package main type T struct { done chan struct{} value int } func X(ctx T) { ctx.done <- struct{}{} } func main() { ctx := T{ done: make(chan struct{}), value: 3, } go X(ctx) <-ctx.done } ================================================ FILE: examples/timeout-behaviour/main.go ================================================ package main import ( "fmt" "time" ) func main() { done := make(chan struct{}) ch := make(chan int) go func(ch chan int, done chan struct{}) { time.Sleep(1 * time.Second) ch <- 42 fmt.Println("Sent") done <- struct{}{} }(ch, done) select { case v := <-ch: fmt.Println("received value of", v) case <-time.After(1 * time.Second): fmt.Println("Timeout: spawn goroutine to cleanup") fmt.Println("value received after cleanup:", <-ch) case <-time.After(1 * time.Second): fmt.Println("Timeout2: spawn goroutine to cleanup") fmt.Println("value received after cleanup:", <-ch) } <-done fmt.Println("All Done") } ================================================ FILE: fairness/fairness.go ================================================ // Package fairness runs a fairness analysis. // // Fairness analysis is an estimation of loop and recursive calls to find // potentially unfair loop and recurse conditions. // - If the loop is a range slice/map expression --> usually safe (finite) // - If the loop is a range channel expression --> safe if channel is closed // - If the loop is an ordinary for-loop --> safe if // * loop condition is not constant or constant expression // * loop index is modified [in the loop body] package fairness import ( "log" "os" "github.com/fatih/color" "github.com/nickng/dingo-hunter/logwriter" "github.com/nickng/dingo-hunter/ssabuilder" "golang.org/x/tools/go/ssa" ) // FairnessAnalysis type FairnessAnalysis struct { unsafe int total int info *ssabuilder.SSAInfo logger *log.Logger } // NewFairnessAnalysis starts a new analysis. func NewFairnessAnalysis() *FairnessAnalysis { return &FairnessAnalysis{unsafe: 0, total: 0} } func (fa *FairnessAnalysis) Visit(fn *ssa.Function) { visitedBlk := make(map[*ssa.BasicBlock]bool) fa.logger.Printf("Visiting: %s", fn.String()) for _, blk := range fn.Blocks { if _, visited := visitedBlk[blk]; !visited { visitedBlk[blk] = true fa.logger.Printf(" block %d %s", blk.Index, blk.Comment) // First consider blocks with loop initialisation blocks. if blk.Comment == "rangeindex.loop" { fa.total++ fa.logger.Println(color.GreenString("✓ range loops are fair")) } else if blk.Comment == "rangechan.loop" { fa.total++ hasClose := false for _, ch := range fa.info.FindChan(blk.Instrs[0].(*ssa.UnOp).X) { if ch.Type == ssabuilder.ChanClose { fa.logger.Println(color.GreenString("✓ found corresponding close() - channel range likely fair")) hasClose = true } } if !hasClose { fa.logger.Println(color.RedString("❌ range over channel w/o close() likely unfair (%s)", fa.info.FSet.Position(blk.Instrs[0].Pos()))) fa.unsafe++ } } else if blk.Comment == "for.loop" { fa.total++ if fa.isLikelyUnsafe(blk) { fa.logger.Println(color.RedString("❌ for.loop maybe bad")) fa.unsafe++ } else { fa.logger.Println(color.GreenString("✓ for.loop is ok")) } } else { // Normal blocks (or loops without initialisation blocks). if len(blk.Instrs) > 1 { if ifInst, ok := blk.Instrs[len(blk.Instrs)-1].(*ssa.If); ok { _, thenVisited := visitedBlk[ifInst.Block().Succs[0]] _, elseVisited := visitedBlk[ifInst.Block().Succs[1]] if thenVisited || elseVisited { // there is a loop! fa.total++ if !fa.isCondFair(ifInst.Cond) { fa.logger.Println(color.YellowString("Warning: recurring block condition probably unfair")) fa.unsafe++ } else { fa.logger.Println(color.GreenString("✓ recurring block is ok")) } } } else if jInst, ok := blk.Instrs[len(blk.Instrs)-1].(*ssa.Jump); ok { if _, visited := visitedBlk[jInst.Block().Succs[0]]; visited { fa.total++ fa.unsafe++ fa.logger.Println(color.RedString("❌ infinite loop or recurring block, probably bad (%s)", fa.info.FSet.Position(blk.Instrs[0].Pos()))) } } } } } } } // isLikelyUnsafe checks if a given "for.loop" block has non-static index and // non-static loop condition. func (fa *FairnessAnalysis) isLikelyUnsafe(blk *ssa.BasicBlock) bool { for _, instr := range blk.Instrs { switch instr := instr.(type) { case *ssa.DebugRef: case *ssa.If: // Last instruction of block if !fa.isCondFair(instr.Cond) { fa.logger.Println(color.YellowString("Warning: loop condition probably unfair")) return true // Definitely unsafe } } } // Reaching here mean the exit cond is not func call or constant if fa.isIndexStatic(blk) { // If index is static or unchanged (i.e. while loop), that means // 1. The exit condition is NOT index // 2. The exit condition dependent on 'outside' variable // TODO(nickng): check that if-condition is used in body fa.logger.Println(color.YellowString("Warning: cannot find loop index")) return true // Assume unsafe } return false } // isCondFair returns true if an if condition (bool expression) is constant. func (fa *FairnessAnalysis) isCondFair(cond ssa.Value) bool { switch cond := cond.(type) { case *ssa.Const: fa.logger.Println(color.YellowString("Warning: loop condition is constant")) return false case *ssa.BinOp: // <, <=, !=, == if _, xConst := cond.X.(*ssa.Const); xConst { if _, yConst := cond.Y.(*ssa.Const); yConst { fa.logger.Println(color.YellowString("Warning: loop condition is constant")) return false } else { fa.logger.Println(color.YellowString("Try to trace back on Y")) } } else { fa.logger.Println(color.YellowString("Try to trace back on X")) } case *ssa.UnOp: if _, con := cond.X.(*ssa.Const); con { fa.logger.Println(color.YellowString("Warning: loop condition is constant")) return false } case *ssa.Call: fa.logger.Println(color.YellowString("Warning:%s: condition is function call --> unsure", fa.info.FSet.Position(cond.Pos()).String())) return false } return true // Assume fair by default } // isIndexStatic returns true if a block does not have a modify-index Phi. func (fa *FairnessAnalysis) isIndexStatic(blk *ssa.BasicBlock) bool { for _, instr := range blk.Instrs { switch instr := instr.(type) { case *ssa.DebugRef: case *ssa.Phi: if len(instr.Comment) > 0 { fa.logger.Println(color.BlueString(" note: Index var %s", instr.Comment)) return false } } } fa.logger.Println(color.BlueString(" note: Index var not found")) return true } // Check for fairness on a built SSA func Check(info *ssabuilder.SSAInfo) { if cgRoot := info.CallGraph(); cgRoot != nil { fa := NewFairnessAnalysis() fa.info = info fa.logger = log.New(logwriter.New(os.Stdout, true, true), "fairness: ", log.LstdFlags) cgRoot.Traverse(fa) if fa.unsafe <= 0 { fa.logger.Printf(color.GreenString("Result: %d/%d is likely unsafe", fa.unsafe, fa.total)) } else { fa.logger.Printf(color.RedString("Result: %d/%d is likely unsafe", fa.unsafe, fa.total)) } } } ================================================ FILE: go.mod ================================================ module github.com/nickng/dingo-hunter require ( github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310 github.com/fatih/color v1.7.0 github.com/nickng/cfsm v1.0.0 github.com/nickng/migo/v3 v3.0.0 github.com/spf13/cobra v0.0.3 github.com/spf13/viper v1.3.1 golang.org/x/tools v0.1.12 ) require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/fsnotify/fsnotify v1.4.7 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/magiconair/properties v1.8.0 // indirect github.com/mattn/go-colorable v0.0.9 // indirect github.com/mattn/go-isatty v0.0.4 // indirect github.com/mitchellh/mapstructure v1.1.2 // indirect github.com/pelletier/go-toml v1.2.0 // indirect github.com/spf13/afero v1.1.2 // indirect github.com/spf13/cast v1.3.0 // indirect github.com/spf13/jwalterweatherman v1.0.0 // indirect github.com/spf13/pflag v1.0.3 // indirect golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.7.0 // indirect gopkg.in/yaml.v2 v2.2.2 // indirect ) ================================================ FILE: go.sum ================================================ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310 h1:t+qxRrRtwNiUYA+Xh2jSXhoG2grnMCMKX4Fg6lx9X1U= github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/nickng/cfsm v1.0.0 h1:Vmn8mSG7pDyhkdVqgQiMXwn3nJG98qDCSbYUKiT9IOg= github.com/nickng/cfsm v1.0.0/go.mod h1:PrQ7n3oDClsXqcGSgpTchal04ATQHQ8QC4NfdB6DzVk= github.com/nickng/migo/v3 v3.0.0 h1:ajAyfE+iscsoJuBB5x1CzoiHRLSacIo5Dpc7wYeEWbg= github.com/nickng/migo/v3 v3.0.0/go.mod h1:f/kWQQkC/OszsYKduE4dffcTsQZgAkCmn41wTlPe6h8= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/viper v1.3.1 h1:5+8j8FTpnFV4nEImW/ofkzEt8VoOiLXxdYIDsB73T38= github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= ================================================ FILE: logwriter/logwriter.go ================================================ // Package logwriter wraps a io.Writer for dingo-hunter logging. // package logwriter // "github.com/nickng/dingo-hunter/logwriter" import ( "bufio" "fmt" "io" "io/ioutil" "log" "os" "github.com/fatih/color" ) // Writer is a log writer and its configurations. type Writer struct { io.Writer LogFile string EnableLogging bool EnableColour bool Cleanup func() } // New creates a new file writer. func NewFile(logfile string, enableLogging, enableColour bool) *Writer { return &Writer{ LogFile: logfile, EnableLogging: enableLogging, EnableColour: enableColour, } } // New creates a new log writer. func New(w io.Writer, enableLogging, enableColour bool) *Writer { return &Writer{ Writer: w, EnableLogging: enableLogging, EnableColour: enableColour, } } // Create initialises a new writer. func (w *Writer) Create() error { color.NoColor = !w.EnableColour if !w.EnableLogging { w.Writer = ioutil.Discard w.Cleanup = func() {} return nil } if w.Writer != nil { w.Cleanup = func() {} return nil } if w.LogFile != "" { if f, err := os.Create(w.LogFile); err != nil { return fmt.Errorf("Failed to create log file: %s", err) } else { bufWriter := bufio.NewWriter(f) w.Writer = bufWriter w.Cleanup = func() { if err := bufWriter.Flush(); err != nil { log.Printf("flush: %s", err) } if err := f.Close(); err != nil { log.Printf("close: %s", err) } } } } else { // Logfile non-empty w.Writer = os.Stdout w.Cleanup = func() {} } return nil } ================================================ FILE: main.go ================================================ // +build go1.7 package main import ( "fmt" "os" "github.com/nickng/dingo-hunter/cmd" ) func main() { if err := cmd.RootCmd.Execute(); err != nil { fmt.Println(err) os.Exit(-1) } } ================================================ FILE: migoextract/call.go ================================================ package migoextract // Functions for handling function call-like instructions // i.e. builtin, call, closure, defer, go. import ( "go/types" "github.com/nickng/migo/v3" "golang.org/x/tools/go/ssa" ) // Call performs call on a given unprepared call context. func (caller *Function) Call(call *ssa.Call, infer *TypeInfer, b *Block, l *Loop) { if call == nil { infer.Logger.Fatal("Call is nil") return } common := call.Common() switch fn := common.Value.(type) { case *ssa.Builtin: switch fn.Name() { case "close": ch, ok := caller.locals[common.Args[0]] if !ok { infer.Logger.Fatalf("call close: %s: %s", common.Args[0].Name(), ErrUnknownValue) return } if paramName, ok := caller.revlookup[ch.String()]; ok { caller.FuncDef.AddStmts(&migo.CloseStatement{Chan: paramName}) } else { if _, ok := common.Args[0].(*ssa.Phi); ok { caller.FuncDef.AddStmts(&migo.CloseStatement{Chan: common.Args[0].Name()}) } else { caller.FuncDef.AddStmts(&migo.CloseStatement{Chan: ch.(*Value).Name()}) } } infer.Logger.Print(caller.Sprintf("close %s", common.Args[0])) return case "len": if l.State == Enter { len, err := caller.callLen(common, infer) if err == ErrRuntimeLen { l.Bound = Dynamic return } l.Bound, l.End = Static, len return } caller.locals[call] = &Value{call, caller.InstanceID(), l.Index} infer.Logger.Printf(caller.Sprintf(" builtin.%s", common.String())) default: infer.Logger.Printf(caller.Sprintf(" builtin.%s", common.String())) } case *ssa.MakeClosure: infer.Logger.Printf(caller.Sprintf(SkipSymbol+" make closure %s", fn.String())) caller.callClosure(common, fn, infer, b, l) case *ssa.Function: if common.StaticCallee() == nil { infer.Logger.Fatal("Call with nil CallCommon") } callee := caller.callFn(common, infer, b, l) if callee != nil { caller.storeRetvals(infer, call.Value(), callee) } default: if !common.IsInvoke() { infer.Logger.Print("Unknown call type", common.String(), common.Description()) return } callee := caller.invoke(common, infer, b, l) if callee != nil { caller.storeRetvals(infer, call.Value(), callee) } else { // Mock out the return values. switch common.Signature().Results().Len() { case 0: case 1: caller.locals[call.Value()] = &External{ parent: caller.Fn, typ: call.Value().Type().Underlying(), } case 2: caller.locals[call.Value()] = &External{typ: call.Value().Type().Underlying()} caller.tuples[caller.locals[call.Value()]] = make(Tuples, common.Signature().Results().Len()) } } } } // Go handles Go statements. func (caller *Function) Go(instr *ssa.Go, infer *TypeInfer) { common := instr.Common() callee := caller.prepareCallFn(common, common.StaticCallee(), nil) spawnStmt := &migo.SpawnStatement{Name: callee.Fn.String(), Params: []*migo.Parameter{}} for i, c := range common.Args { if _, ok := c.Type().(*types.Chan); ok { ch := getChan(c, infer) spawnStmt.AddParams(&migo.Parameter{Caller: ch, Callee: callee.Fn.Params[i]}) } } if inst, ok := caller.locals[common.Value]; ok { if bindings, ok := caller.Prog.closures[inst]; ok { for _, b := range bindings { if v, ok := b.(*Value); ok { if _, ok := derefType(v.Type()).(*types.Chan); ok { spawnStmt.AddParams(&migo.Parameter{Caller: v, Callee: v}) } } } } } caller.FuncDef.AddStmts(spawnStmt) // Don't actually call/visit the function but enqueue it. infer.GQueue = append(infer.GQueue, callee) } // callLen computes the length of a given data structure (if statically known). func (caller *Function) callLen(common *ssa.CallCommon, infer *TypeInfer) (int64, error) { arg0 := common.Args[0] switch t := arg0.Type().(type) { case *types.Array: infer.Logger.Printf(caller.Sprintf(" len(%s %s) = %d", arg0.Name(), arg0.Type(), t.Len())) return t.Len(), nil default: // String = runtime length of string // Map = runtime size of map // Slice = runtime size of slice // Chan = elements in queue infer.Logger.Printf(caller.Sprintf(" len(%s %s) = ?", arg0.Name(), arg0.Type())) } return 0, ErrRuntimeLen } // storeRetvals takes retval (SSA value from caller storing return value(s)) and // stores the return value of function (callee). func (caller *Function) storeRetvals(infer *TypeInfer, retval ssa.Value, callee *Function) { if !callee.HasBody() { switch callee.Fn.Signature.Results().Len() { case 0: // Nothing. case 1: // Creating external instance because return value may be used. caller.locals[retval] = &External{caller.Fn, retval.Type().Underlying(), caller.InstanceID()} infer.Logger.Print(caller.Sprintf(ExitSymbol + "external")) default: caller.locals[retval] = &External{caller.Fn, retval.Type().Underlying(), caller.InstanceID()} caller.tuples[caller.locals[retval]] = make(Tuples, callee.Fn.Signature.Results().Len()) infer.Logger.Print(caller.Sprintf(ExitSymbol+"external len=%d", callee.Fn.Signature.Results().Len())) } return } switch len(callee.retvals) { case 0: // Nothing. case 1: // XXX Pick the last return value from the exit paths // This assumes idiomatic Go for error path to return early // https://golang.org/doc/effective_go.html#if caller.locals[retval] = callee.retvals[len(callee.retvals)-1] if a, ok := callee.arrays[caller.locals[retval]]; ok { caller.arrays[caller.locals[retval]] = a } if s, ok := callee.structs[caller.locals[retval]]; ok { caller.structs[caller.locals[retval]] = s } if m, ok := callee.maps[caller.locals[retval]]; ok { caller.maps[caller.locals[retval]] = m } if a, ok := callee.Prog.arrays[caller.locals[retval]]; ok { caller.arrays[caller.locals[retval]] = a } if s, ok := callee.Prog.structs[caller.locals[retval]]; ok { caller.structs[caller.locals[retval]] = s } switch inst := caller.locals[retval].(type) { case *Value: infer.Logger.Print(caller.Sprintf(ExitSymbol+"[1] %s", inst)) return case *External: infer.Logger.Print(caller.Sprintf(ExitSymbol+"[1] (ext) %s", inst)) return case *Const: infer.Logger.Print(caller.Sprintf(ExitSymbol+"[1] constant %s", inst)) return default: infer.Logger.Fatalf("return[1]: %s: not an instance %+v", ErrUnknownValue, retval) } default: caller.locals[retval] = &Value{retval, caller.InstanceID(), int64(0)} if callee.Fn.Signature.Results().Len() == 1 { caller.locals[retval] = callee.retvals[len(callee.retvals)-1] if a, ok := callee.arrays[caller.locals[retval]]; ok { caller.arrays[caller.locals[retval]] = a } if s, ok := callee.structs[caller.locals[retval]]; ok { caller.structs[caller.locals[retval]] = s } if m, ok := callee.maps[caller.locals[retval]]; ok { caller.maps[caller.locals[retval]] = m } if a, ok := callee.Prog.arrays[caller.locals[retval]]; ok { caller.arrays[caller.locals[retval]] = a } if s, ok := callee.Prog.structs[caller.locals[retval]]; ok { caller.structs[caller.locals[retval]] = s } } else { caller.tuples[caller.locals[retval]] = make(Tuples, callee.Fn.Signature.Results().Len()) for i := range callee.retvals { tupleIdx := i % callee.Fn.Signature.Results().Len() if callee.retvals[i] != nil { caller.tuples[caller.locals[retval]][tupleIdx] = callee.retvals[i] } if a, ok := callee.arrays[callee.retvals[i]]; ok { caller.arrays[callee.retvals[i]] = a } if s, ok := callee.structs[callee.retvals[i]]; ok { caller.structs[callee.retvals[i]] = s } if m, ok := callee.maps[callee.retvals[i]]; ok { caller.maps[callee.retvals[i]] = m } if a, ok := callee.Prog.arrays[callee.retvals[i]]; ok { caller.arrays[callee.retvals[i]] = a } if s, ok := callee.Prog.structs[callee.retvals[i]]; ok { caller.structs[callee.retvals[i]] = s } } } // XXX Pick the return values from the last exit path // This assumes idiomatic Go for error path to return early // https://golang.org/doc/effective_go.html#if infer.Logger.Print(caller.Sprintf(ExitSymbol+"[%d/%d] %v", callee.Fn.Signature.Results().Len(), len(callee.retvals), caller.tuples[caller.locals[retval]])) } } // IsRecursiveCall checks if current function context is a recursive call and marks // the context recursive (with pointer to the original context). func (caller *Function) IsRecursiveCall() bool { for parentCtx := caller.Caller; parentCtx.Caller != nil; parentCtx = parentCtx.Caller { if caller.Fn == parentCtx.Fn { // is identical function? return true } } return false } func (caller *Function) invoke(common *ssa.CallCommon, infer *TypeInfer, b *Block, l *Loop) *Function { iface, ok := common.Value.Type().Underlying().(*types.Interface) if !ok { infer.Logger.Fatalf("invoke: %s is not an interface", common.String()) return nil } ifaceInst, ok := caller.locals[common.Value] // SSA value initialised if !ok { infer.Logger.Fatalf("invoke: %s: %s", common.Value.Name(), ErrUnknownValue) return nil } switch inst := ifaceInst.(type) { case *Value: // OK case *Const: if inst.Const.IsNil() { return nil } infer.Logger.Fatalf("invoke: %+v is not nil nor concrete", ifaceInst) case *External: infer.Logger.Printf(caller.Sprintf("invoke: %+v external", ifaceInst)) return nil default: infer.Logger.Printf(caller.Sprintf("invoke: %+v unknown", ifaceInst)) return nil } meth, _ := types.MissingMethod(ifaceInst.(*Value).Type(), iface, true) // static if meth != nil { meth, _ = types.MissingMethod(ifaceInst.(*Value).Type(), iface, false) // non-static if meth != nil { infer.Logger.Printf("invoke: missing method %s: %s", meth.String(), ErrIfaceIncomplete) return nil } } fn := findMethod(common.Value.Parent().Prog, common.Method, ifaceInst.(*Value).Type(), infer) if fn == nil { if meth == nil { infer.Logger.Printf("invoke: cannot locate concrete method") } else { infer.Logger.Printf("invoke: cannot locate concrete method: %s", meth.String()) } return nil } return caller.call(common, fn, common.Value, infer, b, l) } func (caller *Function) callFn(common *ssa.CallCommon, infer *TypeInfer, b *Block, l *Loop) *Function { return caller.call(common, common.StaticCallee(), nil, infer, b, l) } func (caller *Function) call(common *ssa.CallCommon, fn *ssa.Function, rcvr ssa.Value, infer *TypeInfer, b *Block, l *Loop) *Function { callee := caller.prepareCallFn(common, fn, rcvr) if callee.IsRecursiveCall() { return callee } visitFunc(callee.Fn, infer, callee) if callee.HasBody() { callStmt := &migo.CallStatement{Name: callee.Fn.String(), Params: []*migo.Parameter{}} for i, c := range common.Args { if _, ok := c.Type().(*types.Chan); ok { ch := getChan(c, infer) callStmt.AddParams(&migo.Parameter{Caller: ch, Callee: callee.Fn.Params[i]}) } } if inst, ok := caller.locals[common.Value]; ok { if bindings, ok := caller.Prog.closures[inst]; ok { for _, b := range bindings { if v, ok := b.(*Value); ok { if _, ok := derefType(v.Type()).(*types.Chan); ok { callStmt.AddParams(&migo.Parameter{Caller: v, Callee: v}) } } } } } caller.FuncDef.AddStmts(callStmt) } return callee } func (caller *Function) callClosure(common *ssa.CallCommon, closure *ssa.MakeClosure, infer *TypeInfer, b *Block, l *Loop) { callee := caller.prepareCallFn(common, closure.Fn.(*ssa.Function), nil) for _, b := range closure.Bindings { if inst, ok := caller.locals[b]; ok { callee.locals[b] = inst } } callee.call(common, common.StaticCallee(), nil, infer, b, l) } func findMethod(prog *ssa.Program, meth *types.Func, typ types.Type, infer *TypeInfer) *ssa.Function { if meth != nil { return prog.LookupMethod(typ, meth.Pkg(), meth.Name()) } infer.Logger.Fatal(ErrMethodNotFound) return nil } ================================================ FILE: migoextract/chan.go ================================================ package migoextract // Utility functions to work with channels. import ( "go/types" "golang.org/x/tools/go/ssa" ) func getChan(val ssa.Value, infer *TypeInfer) ssa.Value { if _, ok := val.Type().(*types.Chan); ok { switch instr := val.(type) { case *ssa.ChangeType: return getChan(instr.X, infer) case *ssa.Parameter: return val // Maybe lookup from parent case *ssa.MakeChan: return val case *ssa.Phi: return val } } infer.Logger.Print("Don't know where this chan comes from:", val.String()) return val } ================================================ FILE: migoextract/closure.go ================================================ package migoextract // Captures are holders for closure capture variables. type Captures []Instance ================================================ FILE: migoextract/commaok.go ================================================ package migoextract // CommaOK tests. import ( "golang.org/x/tools/go/ssa" ) // CommaOK is a struct to capture different kinds of the // _, ok := instr syntax where instr can be TypeAssert, map Lookup or recv UnOp type CommaOk struct { Instr ssa.Instruction // TypeAssert, Lookup (map access) or UnOp (recv). Result Instance // Result tuple { recvVal:T , recvTest:bool }. OkCond Instance // The comma-ok condition. } func isCommaOk(f *Function, inst Instance) bool { for _, commaOk := range f.commaok { if commaOk.OkCond == inst { return true } } return false } ================================================ FILE: migoextract/context.go ================================================ package migoextract // Context captures variables (and invariants) of scopes during execution. // Different contexts are used for different level of fine-grainedness. import ( "bytes" "fmt" "go/types" "log" "github.com/nickng/migo/v3" "golang.org/x/tools/go/ssa" ) // LoopState indicate the state (in analysis) of loop currently in. type LoopState int //go:generate stringer -type=LoopState const ( NonLoop LoopState = iota Enter // Loop initialisation and condition checking Body // Loop body (repeat) Exit // Loop exit ) // LoopBound indicates if a loop is bounded or not. type LoopBound int //go:generate stringer -type=LoopBound const ( Unknown LoopBound = iota Static // Static loop. Dynamic // Dynamic loop. ) // Program captures the program environment. // // A single inference has exactly one Program, and it contains all global // data (and metadata) in the program. type Program struct { FuncInstance map[*ssa.Function]int // Count number of function instances. InitPkgs map[*ssa.Package]bool // Initialised packages. Infer *TypeInfer // Reference to inference. MigoProg *migo.Program // Core calculus of program. closures map[Instance]Captures // Closures. globals map[ssa.Value]Instance // Global variables. *Storage // Storage. } // NewProgram creates a program for a type inference. func NewProgram(infer *TypeInfer) *Program { return &Program{ FuncInstance: make(map[*ssa.Function]int), InitPkgs: make(map[*ssa.Package]bool), Infer: infer, closures: make(map[Instance]Captures), globals: make(map[ssa.Value]Instance), Storage: NewStorage(), } } // Function captures the function environment. // // Function environment stores local variable instances (as reference), return // values, if-then-else parent, select-condition. type Function struct { Fn *ssa.Function // Function callee (this). Caller *Function // Function caller (parent). Prog *Program // Program environment (global). Visited map[*ssa.BasicBlock]int // Visited block tracking. Level int // Call level (for indentation). FuncDef *migo.Function // Function definition. ChildBlocks map[int]*Block // Map from index -> child SSA blocks. id int // Instance identifier. hasBody bool // True if function has body. commaok map[Instance]*CommaOk // CommaOK statements. defers []*ssa.Defer // Deferred calls. locals map[ssa.Value]Instance // Local variable instances. revlookup map[string]string // Reverse lookup names. extraargs []ssa.Value retvals []Instance // Return value instances. selects map[Instance]*Select // Select cases mapping. tuples map[Instance]Tuples // Tuples. loopstack *LoopStack // Stack of Loop. *Storage // Storage. } // NewMainFunction returns a new main() call context. func NewMainFunction(prog *Program, mainFn *ssa.Function) *Function { return &Function{ Fn: mainFn, Prog: prog, Visited: make(map[*ssa.BasicBlock]int), FuncDef: migo.NewFunction("main.main"), ChildBlocks: make(map[int]*Block), commaok: make(map[Instance]*CommaOk), defers: []*ssa.Defer{}, locals: make(map[ssa.Value]Instance), retvals: []Instance{}, extraargs: []ssa.Value{}, revlookup: make(map[string]string), selects: make(map[Instance]*Select), tuples: make(map[Instance]Tuples), loopstack: NewLoopStack(), Storage: NewStorage(), } } // NewFunction returns a new function call context, and takes the caller's // context as parameter. func NewFunction(caller *Function) *Function { return &Function{ Caller: caller, Prog: caller.Prog, Visited: make(map[*ssa.BasicBlock]int), FuncDef: migo.NewFunction("__uninitialised__"), Level: caller.Level + 1, ChildBlocks: make(map[int]*Block), commaok: make(map[Instance]*CommaOk), defers: []*ssa.Defer{}, locals: make(map[ssa.Value]Instance), revlookup: make(map[string]string), extraargs: []ssa.Value{}, retvals: []Instance{}, selects: make(map[Instance]*Select), tuples: make(map[Instance]Tuples), loopstack: NewLoopStack(), Storage: NewStorage(), } } // HasBody returns true if Function is user-defined or has source code and // built in SSA program. func (caller *Function) HasBody() bool { return caller.hasBody } // prepareCallFn prepares a caller Function to visit performing necessary context switching and returns a new callee Function. // rcvr is non-nil if invoke call func (caller *Function) prepareCallFn(common *ssa.CallCommon, fn *ssa.Function, rcvr ssa.Value) *Function { callee := NewFunction(caller) callee.Fn = fn // This function was called before if _, ok := callee.Prog.FuncInstance[callee.Fn]; ok { callee.Prog.FuncInstance[callee.Fn]++ } else { callee.Prog.FuncInstance[callee.Fn] = 0 } callee.FuncDef.Name = fn.String() callee.id = callee.Prog.FuncInstance[callee.Fn] for i, param := range callee.Fn.Params { var argCaller ssa.Value if rcvr != nil { if i == 0 { argCaller = rcvr } else { argCaller = common.Args[i-1] } } else { argCaller = common.Args[i] } if _, ok := argCaller.Type().(*types.Chan); ok { callee.FuncDef.AddParams(&migo.Parameter{Caller: argCaller, Callee: param}) } if inst, ok := caller.locals[argCaller]; ok { callee.locals[param] = inst callee.revlookup[argCaller.Name()] = param.Name() // Copy array and struct from parent. if elems, ok := caller.arrays[inst]; ok { callee.arrays[inst] = elems } if fields, ok := caller.structs[inst]; ok { callee.structs[inst] = fields } if maps, ok := caller.maps[inst]; ok { callee.maps[inst] = maps } } else if c, ok := argCaller.(*ssa.Const); ok { callee.locals[param] = &Const{c} } } if inst, ok := caller.locals[common.Value]; ok { if cap, ok := caller.Prog.closures[inst]; ok { for i, fv := range callee.Fn.FreeVars { callee.locals[fv] = cap[i] if _, ok := derefType(fv.Type()).(*types.Chan); ok { callee.FuncDef.AddParams(&migo.Parameter{Caller: fv, Callee: fv}) } } } } return callee } // InstanceID returns the current function instance number (numbers of times // function called). func (caller *Function) InstanceID() int { if caller.id < 0 { log.Fatal(ErrUnitialisedFunc) } return caller.id } func (caller *Function) String() string { var buf bytes.Buffer buf.WriteString("--- Context ---\n") if caller.Fn == nil { log.Fatal(ErrUnitialisedFunc) } buf.WriteString(fmt.Sprintf("\t- Fn:\t%s_%d\n", caller.Fn, caller.id)) if caller.Caller != nil { buf.WriteString(fmt.Sprintf("\t- Parent:\t%s\n", caller.Caller.Fn.String())) } else { buf.WriteString("\t- Parent: main.main\n") } for val, instance := range caller.locals { buf.WriteString(fmt.Sprintf("\t\t- %s = %s\n", val.Name(), instance)) } buf.WriteString(fmt.Sprintf("\t- Retvals: %d\n", len(caller.retvals))) return buf.String() } func (caller *Function) updateInstances(old, new Instance) { for inst, array := range caller.arrays { for k, v := range array { if v == old { caller.arrays[inst][k] = new } } } for inst, array := range caller.Prog.arrays { for k, v := range array { if v == old { caller.Prog.arrays[inst][k] = new } } } for inst, struc := range caller.structs { for i, field := range struc { if field == old { caller.structs[inst][i] = new } } } for inst, struc := range caller.Prog.structs { for i, field := range struc { if field == old { caller.Prog.structs[inst][i] = new } } } for inst, mmap := range caller.maps { for k, v := range mmap { if v == old { caller.maps[inst][k] = new } } } } // Block captures information about SSA block. type Block struct { Function *Function // Parent function context. MigoDef *migo.Function // MiGo Function for the block. Pred int // Immediate predecessor trace. Index int // Current block index. } // NewBlock creates a new block enclosed by the given function. func NewBlock(parent *Function, block *ssa.BasicBlock, curr int) *Block { blockFn := fmt.Sprintf("%s#%d", parent.Fn.String(), block.Index) parent.ChildBlocks[block.Index] = &Block{ Function: parent, MigoDef: migo.NewFunction(blockFn), Pred: curr, Index: block.Index, } return parent.ChildBlocks[block.Index] } // Context is a grouping of different levels of context. type Context struct { F *Function // Function context. B *Block // Block context. L *Loop // Loop context. } ================================================ FILE: migoextract/datastructure.go ================================================ package migoextract // Data structure utilities. import ( "go/types" "golang.org/x/tools/go/ssa" ) // Elems are maps from array indices (variable) to VarInstances of elements. type Elems map[ssa.Value]Instance // Fields is a slice of variable instances. type Fields []Instance func (caller *Function) getStructField(struc ssa.Value, idx int) (Instance, error) { if instance, ok := caller.locals[struc]; ok { if fields, ok := caller.structs[instance]; ok { return fields[idx], nil } else if fields, ok := caller.Prog.structs[instance]; ok { return fields[idx], nil } } return nil, ErrInvalidVarRead } func (caller *Function) setStructField(struc ssa.Value, idx int, instance Instance) { if instance, ok := caller.locals[struc]; ok { if _, ok := caller.structs[instance]; ok { caller.structs[instance][idx] = instance return } else if _, ok := caller.Prog.structs[instance]; ok { caller.Prog.structs[instance][idx] = instance return } } } // initNestedRefVar initialises empty reference data structures {array,slice,struct} not used // before func initNestedRefVar(infer *TypeInfer, ctx *Context, inst Instance, heap bool) { var s *Storage if heap { s = ctx.F.Prog.Storage } else { s = ctx.F.Storage } v, ok := inst.(*Value) if !ok { return } switch t := derefAllType(v.Type()).Underlying().(type) { case *types.Array: if _, ok := s.arrays[inst]; !ok { s.arrays[inst] = make(Elems, t.Len()) infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"initialised %s as array (type: %s)", inst, v.Type())) } case *types.Slice: if _, ok := s.arrays[inst]; !ok { s.arrays[inst] = make(Elems, 0) infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"initialised %s as slice (type: %s)", inst, v.Type())) } case *types.Struct: if _, ok := s.structs[inst]; !ok { s.structs[inst] = make(Fields, t.NumFields()) infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"initialised %s as struct (type: %s)", inst, v.Type())) } default: } } ================================================ FILE: migoextract/error.go ================================================ package migoextract // Predefined errors import "errors" var ( ErrEmptyStack = errors.New("stack: empty") ErrNoMainPkg = errors.New("no main package found") ErrNonConstChanBuf = errors.New("MakeChan creates channel with non-const buffer size") ErrMakeChanNonChan = errors.New("type error: MakeChan creates non-channel type channel") ErrUnitialisedFunc = errors.New("operation on uninitialised function (did you call prepareVisit?)") ErrUnknownValue = errors.New("internal error: unknown SSA value") ErrInvalidJumpSucc = errors.New("internal error: wrong number of Succ for Jump (expects 1)") ErrInvalidIfSucc = errors.New("internal error: wrong number of Succ for If (expects 2)") ErrUnimplemented = errors.New("unimplemented") ErrWrongArgNum = errors.New("wrong number of arguments") ErrRuntimeLen = errors.New("length can only be determined at runtime") ErrInvalidVarWrite = errors.New("internal error: write to uninitialised variable") ErrInvalidVarRead = errors.New("internal error: read from uninitialised variable") ErrIfaceIncomplete = errors.New("interface not fully implemented") ErrMethodNotFound = errors.New("interface method not found") ErrPhiUnknownEdge = errors.New("phi node has edge from unknown block") ErrIncompatType = errors.New("cannot convert incompatible type") ) ================================================ FILE: migoextract/instance.go ================================================ package migoextract // Wrapper of values and constants in an SSA program. // Designed for tracking usage and instances of values used in SSA program. import ( "bytes" "fmt" "go/constant" "go/types" "golang.org/x/tools/go/ssa" ) // Instance is an interface for an instance of a defined value. type Instance interface { Instance() (int, int) String() string } // Value captures a specific instance of a SSA value by counting the number of // instantiations. type Value struct { ssa.Value // Storing the ssa.Value this instance is for. instID int // Instance number (default: 0). loopIdx int64 // Loop index associated with var (default: 0). } // Instance returns the instance identifier pair. func (i *Value) Instance() (int, int) { return i.instID, int(i.loopIdx) } func (i *Value) String() string { var prefix bytes.Buffer if i.Parent() != nil { prefix.WriteString(i.Parent().String()) } else { prefix.WriteString("__main__") } return fmt.Sprintf("%s.%s_%d_%d", prefix.String(), i.Name(), i.instID, i.loopIdx) } // Placeholder is a temporary stand in for actual SSA Value. type Placeholder struct { } // Instance returns the instance number. func (i *Placeholder) Instance() (int, int) { return -1, -1 } func (i *Placeholder) String() string { return fmt.Sprintf("placeholder instance") } // External captures an external instance of an SSA value. // // An external instance is one without ssa.Value, usually if the creating body // is in runtime or not built as SSA. type External struct { parent *ssa.Function // Parent (enclosing) function. typ types.Type // Type of returned instance. instID int // Instance number (default: 0). } // Instance returns the instance number. func (i *External) Instance() (int, int) { return i.instID, 0 } func (i *External) String() string { var prefix bytes.Buffer if i.parent != nil { prefix.WriteString(i.parent.String()) } else { prefix.WriteString("__unknown__") } return fmt.Sprintf("%b.%s_%d:%s", prefix, "__ext", i.instID, i.typ.String()) } // Const captures a constant value. // // This is just a wrapper. type Const struct { *ssa.Const } // Instance returns the instance identifier pair. func (c *Const) Instance() (int, int) { return 0, 0 } func (c *Const) String() string { switch c.Const.Value.Kind() { case constant.Bool: return fmt.Sprintf("%s", c.Const.String()) case constant.Complex: return fmt.Sprintf("%v", c.Const.Complex128()) case constant.Float: return fmt.Sprintf("%f", c.Const.Float64()) case constant.Int: return fmt.Sprintf("%d", c.Const.Int64()) case constant.String: return fmt.Sprintf("%s", c.Const.String()) default: panic("unknown constant type") } } ================================================ FILE: migoextract/loop.go ================================================ package migoextract // Loop related functions and utilities. import ( "fmt" "go/constant" "go/token" "golang.org/x/tools/go/ssa" ) // Loop captures information about loop. // // A Loop context exists within a function, inside the scope of a for loop. // Nested loops should be captured externally. type Loop struct { Parent *Function // Enclosing function. Bound LoopBound // Loop bound type. State LoopState // Loop/Body/Done. IndexVar ssa.Value // Variable holding the index (phi). CondVar ssa.Value // Variable holding the cond expression. Index int64 // Current index value. Start int64 // Lower bound of index. Step int64 // Increment (can be negative). End int64 // Upper bound of index. LoopBlock int // Block number of loop (with for.loop label). } // SetInit sets the loop index initial value (int). func (l *Loop) SetInit(index ssa.Value, init int64) { l.IndexVar = index l.Start = init l.Index = init } // SetStep sets the loop index step value (int). func (l *Loop) SetStep(step int64) { l.Step = step } // SetCond sets the loop exit condition (int). func (l *Loop) SetCond(cond ssa.Value, max int64) { l.CondVar = cond l.End = max } // Next performs an index increment (e.g. i++) if possible. func (l *Loop) Next() { if l.Bound == Static { l.Index += l.Step } } // HasNext returns true if the loop should continue. func (l *Loop) HasNext() bool { if l.Bound == Static { return l.Start <= l.Index && l.Index <= l.End } return false } func (l *Loop) String() string { if l.Bound != Unknown && l.State != NonLoop { return fmt.Sprintf("%s: bound %s [%d..%d..%d] Step:%d", l.State, l.Bound, l.Start, l.Index, l.End, l.Step) } return fmt.Sprintf("%s: bound %s", l.State, l.Bound) } // loopSetIndex handles loop indices (initial value and increment) func loopSetIndex(instr *ssa.Phi, infer *TypeInfer, ctx *Context) { if i, ok := instr.Edges[0].(*ssa.Const); ok && !i.IsNil() && i.Value.Kind() == constant.Int { ctx.L.SetInit(instr, i.Int64()) infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"%s <= i", fmtLoopHL(ctx.L.Start))) } if bin, ok := instr.Edges[1].(*ssa.BinOp); ok { switch bin.Op { case token.ADD: if i, ok := bin.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetStep(i.Int64()) } infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i += %s", fmtLoopHL(ctx.L.Step))) case token.SUB: if i, ok := bin.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetStep(-i.Int64()) } infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i -= %s", fmtLoopHL(ctx.L.Step))) default: infer.Logger.Printf("loop index expression not supported %s", bin) } } } // loopDetectBounds detects static bounds (or set as dynamic bounds) from based // on loop state machine. func loopDetectBounds(instr *ssa.Phi, infer *TypeInfer, ctx *Context) { switch ctx.L.State { case Enter: switch ctx.L.Bound { case Unknown: phiSelectEdge(instr, infer, ctx) loopSetIndex(instr, infer, ctx) case Static: switch ctx.L.Bound { case Static: phiSelectEdge(instr, infer, ctx) if instr == ctx.L.IndexVar { ctx.L.Next() infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"Increment %s by %s to %s", ctx.L.IndexVar.Name(), fmtLoopHL(ctx.L.Step), fmtLoopHL(ctx.L.Index))) } default: visitSkip(instr, infer, ctx) } case Dynamic: phiSelectEdge(instr, infer, ctx) infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"(dynamic bound) %s", instr.String())) } default: phiSelectEdge(instr, infer, ctx) } } // loopStateTransition updates loop transitions based on the state machine. // // ... NonLoop --> Enter --> Body --> Exit ... // <-- func loopStateTransition(blk *ssa.BasicBlock, infer *TypeInfer, f *Function, l **Loop) { switch (*l).State { case NonLoop: if blk.Comment == "for.loop" { (*l).State = Enter (*l).Bound = Unknown (*l).LoopBlock = blk.Index } if blk.Comment == "for.body" { (*l).State = Body (*l).Bound = Unknown (*l).LoopBlock = blk.Index } case Enter: if blk.Comment == "for.body" { (*l).State = Body } if blk.Comment == "for.done" { (*l).State = Exit top, err := f.loopstack.Pop() if err != nil { return } *l = top } case Body: if blk.Comment == "for.loop" { if (*l).LoopBlock == blk.Index { // Back to loop init, but we don't need to find loop bounds (*l).State = Enter } else { if (*l).IndexVar != nil { infer.Logger.Print(f.Sprintf(LoopSymbol+"enter NESTED loop (%s)", (*l).IndexVar.Name())) } else { infer.Logger.Print(f.Sprintf(LoopSymbol + "enter NESTED loop")) } f.loopstack.Push(*l) *l = &Loop{Parent: f, Bound: Unknown, State: Enter, LoopBlock: blk.Index} } } if blk.Comment == "for.done" { (*l).State = Exit } case Exit: (*l).State = NonLoop (*l).Bound = Unknown } } ================================================ FILE: migoextract/loopbound_string.go ================================================ // generated by stringer -type=LoopBound; DO NOT EDIT package migoextract import "fmt" const _LoopBound_name = "UnknownStaticDynamic" var _LoopBound_index = [...]uint8{0, 7, 13, 20} func (i LoopBound) String() string { if i < 0 || i >= LoopBound(len(_LoopBound_index)-1) { return fmt.Sprintf("LoopBound(%d)", i) } return _LoopBound_name[_LoopBound_index[i]:_LoopBound_index[i+1]] } ================================================ FILE: migoextract/loopstack.go ================================================ package migoextract import ( "sync" ) // LoopStack is a stack of ssa.BasicBlock type LoopStack struct { sync.Mutex s []*Loop } // NewLoopStack creates a new LoopStack. func NewLoopStack() *LoopStack { return &LoopStack{s: []*Loop{}} } // Push adds a new LoopContext to the top of stack. func (s *LoopStack) Push(l *Loop) { s.Lock() defer s.Unlock() s.s = append(s.s, l) } // Pop removes a BasicBlock from top of stack. func (s *LoopStack) Pop() (*Loop, error) { s.Lock() defer s.Unlock() size := len(s.s) if size == 0 { return nil, ErrEmptyStack } l := s.s[size-1] s.s = s.s[:size-1] return l, nil } // IsEmpty returns true if stack is empty. func (s *LoopStack) IsEmpty() bool { return len(s.s) == 0 } ================================================ FILE: migoextract/loopstate_string.go ================================================ // generated by stringer -type=LoopState; DO NOT EDIT package migoextract import "fmt" const _LoopState_name = "NonLoopEnterBodyExit" var _LoopState_index = [...]uint8{0, 7, 12, 16, 20} func (i LoopState) String() string { if i < 0 || i >= LoopState(len(_LoopState_index)-1) { return fmt.Sprintf("LoopState(%d)", i) } return _LoopState_name[_LoopState_index[i]:_LoopState_index[i+1]] } ================================================ FILE: migoextract/migoextract.go ================================================ // Package migoextract provides session type inference from Go code. // package migoextract // import "github.com/nickng/dingo-hunter/migoextract" import ( "go/types" "io" "log" "time" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/nickng/migo/v3" "golang.org/x/tools/go/ssa" ) // TypeInfer contains the metadata for a type inference. type TypeInfer struct { SSA *ssabuilder.SSAInfo // SSA IR of program. Env *Program // Analysed program. GQueue []*Function // Goroutines to be analysed. Time time.Duration Logger *log.Logger Done chan struct{} Error chan error } // New creates a new session type infer analysis. func New(ssainfo *ssabuilder.SSAInfo, inferlog io.Writer) (*TypeInfer, error) { infer := &TypeInfer{ SSA: ssainfo, Logger: log.New(inferlog, "migoextract: ", ssainfo.BuildConf.LogFlags), Done: make(chan struct{}), Error: make(chan error, 1), } return infer, nil } // Run executes the analysis. func (infer *TypeInfer) Run() { infer.Logger.Println("---- Start Analysis ----") // Initialise session. infer.Env = NewProgram(infer) infer.Env.MigoProg = migo.NewProgram() startTime := time.Now() mainPkg := ssabuilder.MainPkg(infer.SSA.Prog) if mainPkg == nil { infer.Error <- ErrNoMainPkg } defer close(infer.Done) initFn := mainPkg.Func("init") mainFn := mainPkg.Func("main") ctx := NewMainFunction(infer.Env, mainFn) // TODO(nickng): inline initialisation of var declarations for _, pkg := range infer.SSA.Prog.AllPackages() { for _, memb := range pkg.Members { switch value := memb.(type) { case *ssa.Global: ctx.Prog.globals[value] = &Value{Value: value} switch t := derefAllType(value.Type()).Underlying().(type) { case *types.Array: ctx.Prog.arrays[ctx.Prog.globals[value]] = make(Elems, t.Len()) case *types.Slice: ctx.Prog.arrays[ctx.Prog.globals[value]] = make(Elems, 0) case *types.Struct: ctx.Prog.structs[ctx.Prog.globals[value]] = make(Fields, t.NumFields()) default: } } } } visitFunc(initFn, infer, ctx) visitFunc(mainFn, infer, ctx) infer.RunQueue() infer.Time = time.Now().Sub(startTime) } // RunQueue executes the analysis on spawned (queued) goroutines. func (infer *TypeInfer) RunQueue() { for _, ctx := range infer.GQueue { infer.Logger.Printf("----- Goroutine %s -----", ctx.Fn.String()) visitFunc(ctx.Fn, infer, ctx) } } ================================================ FILE: migoextract/phi.go ================================================ package migoextract // Deal with Phi nodes. import ( "golang.org/x/tools/go/ssa" ) // phiSelectEdge selects edge based on predecessor block and returns the edge index. func phiSelectEdge(instr *ssa.Phi, infer *TypeInfer, ctx *Context) (edge int) { for i, pred := range instr.Block().Preds { if pred.Index == ctx.B.Pred { e, ok := ctx.F.locals[instr.Edges[i]] if !ok { switch t := instr.Edges[i].(type) { case *ssa.Const: ctx.F.locals[instr.Edges[i]] = &Const{t} e, edge = ctx.F.locals[instr.Edges[i]], pred.Index infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"%s/%s = %s, selected const from block %d", instr.Name(), e, instr.String(), edge)) case *ssa.Function: ctx.F.locals[instr.Edges[i]] = &Value{instr.Edges[i], ctx.F.InstanceID(), ctx.L.Index} e, edge = ctx.F.locals[instr.Edges[i]], pred.Index infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"%s/%s = %s, selected function from block %d", instr.Name(), e, instr.String(), edge)) case *ssa.Call: ctx.F.locals[instr.Edges[i]] = &Value{instr.Edges[i], ctx.F.InstanceID(), ctx.L.Index} e, edge = ctx.F.locals[instr.Edges[i]], pred.Index infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"%s/%s = %s, selected call from block %d", instr.Name(), e, instr.String(), edge)) case *ssa.UnOp: ctx.F.locals[instr.Edges[i]] = &Value{instr.Edges[i], ctx.F.InstanceID(), ctx.L.Index} e, edge = ctx.F.locals[instr.Edges[i]], pred.Index infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"%s/%s = %s, selected UnOp from block %d", instr.Name(), e, instr.String(), edge)) default: infer.Logger.Fatalf("phi: create instance Edge[%d]=%#v: %s", i, instr.Edges[i], ErrUnknownValue) return } } ctx.F.locals[instr], edge = e, pred.Index infer.Logger.Printf(ctx.F.Sprintf(PhiSymbol+"%s/%s = %s, selected from block %d", instr.Name(), e, instr.String(), edge)) ctx.F.revlookup[instr.Name()] = instr.Edges[i].Name() if a, ok := ctx.F.arrays[e]; ok { ctx.F.arrays[ctx.F.locals[instr]] = a } if s, ok := ctx.F.structs[e]; ok { ctx.F.structs[ctx.F.locals[instr]] = s } return } } infer.Logger.Fatalf("phi: %d->%d: %s", ctx.B.Pred, instr.Block().Index, ErrPhiUnknownEdge) return } ================================================ FILE: migoextract/pointer.go ================================================ package migoextract // Utility functions for dealing with pointers. import ( "go/types" ) // derefType dereferences a pointer type once. func derefType(t types.Type) types.Type { if p, ok := t.Underlying().(*types.Pointer); ok { return p.Elem() } return t } // derefAllType dereferences a pointer type until its base type. func derefAllType(t types.Type) types.Type { baseT := t for { if p, ok := baseT.Underlying().(*types.Pointer); ok { baseT = p.Elem() } else { return baseT } } } ================================================ FILE: migoextract/print.go ================================================ package migoextract // Printing and formatting utilities. import ( "fmt" "strings" "github.com/fatih/color" ) const ( BlockSymbol = "┝ " CallSymbol = " ↘ " ExitSymbol = " ↙ " ChanSymbol = "ν " FuncEnterSymbol = "┌" FuncExitSymbol = "└" RecvSymbol = "❓ " SendSymbol = "❗ " SkipSymbol = "┆ " SpawnSymbol = "┿ " JumpSymbol = " ⇾ " ParamSymbol = "├param " MoreSymbol = "┊ " ReturnSymbol = "⏎ " LoopSymbol = "↻ " PhiSymbol = "φ " IfSymbol = "⨁ " SelectSymbol = "Sel:" SplitSymbol = "分" ErrorSymbol = " ◹ " FieldSymbol = " ↦ " NewSymbol = "新" SubSymbol = " ▸ " ValSymbol = "├ " AssignSymbol = "≔" ) var ( fmtBlock = color.New(color.Italic).SprintFunc() fmtChan = color.New(color.FgRed, color.Bold).SprintFunc() fmtClose = color.New(color.FgGreen, color.Bold).SprintFunc() fmtLoopHL = color.New(color.FgHiRed, color.Italic).SprintFunc() fmtPos = color.New(color.FgYellow, color.Italic).SprintFunc() fmtRecv = color.New(color.FgHiBlue).SprintFunc() fmtSend = color.New(color.FgCyan).SprintFunc() fmtSpawn = color.New(color.FgMagenta, color.Bold).SprintFunc() fmtType = color.New(color.BgBlue).SprintFunc() ) // Sprintf in current function context. func (ctx *Function) Sprintf(format string, a ...interface{}) string { return fmt.Sprintf(strings.Repeat(" ", ctx.Level*2)+format, a...) } ================================================ FILE: migoextract/select.go ================================================ package migoextract import ( "github.com/nickng/migo/v3" "golang.org/x/tools/go/ssa" ) // Select keeps track of select statement and its branches. type Select struct { Instr *ssa.Select // Select SSA instruction. MigoStmt *migo.SelectStatement // Select statement in MiGo. Index Instance // Index (extracted from Select instruction). } ================================================ FILE: migoextract/storage.go ================================================ package migoextract // Storage is a grouping of auxiliary extra storage. type Storage struct { arrays map[Instance]Elems // Array elements. maps map[Instance]map[Instance]Instance // Maps. structs map[Instance]Fields // Struct fields. } // NewStorage creates a new storage. func NewStorage() *Storage { return &Storage{ arrays: make(map[Instance]Elems), maps: make(map[Instance]map[Instance]Instance), structs: make(map[Instance]Fields), } } ================================================ FILE: migoextract/tuple.go ================================================ package migoextract // Tuple utils. // Tuples are return values for function that returns multiple. // Can be extracted by ssa.Extract. type Tuples []Instance ================================================ FILE: migoextract/visit.go ================================================ package migoextract import ( "fmt" "go/constant" "go/token" "go/types" "github.com/nickng/migo/v3" "golang.org/x/tools/go/ssa" ) // visitFunc analyses function body. func visitFunc(fn *ssa.Function, infer *TypeInfer, f *Function) { infer.Env.MigoProg.AddFunction(f.FuncDef) infer.Logger.Printf(f.Sprintf(FuncEnterSymbol+"───── func %s ─────", fn.Name())) defer infer.Logger.Printf(f.Sprintf(FuncExitSymbol+"───── func %s ─────", fn.Name())) if fn.Name() == "init" { if _, ok := f.Prog.InitPkgs[fn.Package()]; !ok { f.Prog.InitPkgs[fn.Package()] = true } f.hasBody = true return } for val, instance := range f.locals { infer.Logger.Printf(f.Sprintf(ParamSymbol+"%s = %s", val.Name(), instance)) f.revlookup[instance.String()] = val.Name() // If it comes from params.. } if fn.Blocks == nil { infer.Logger.Print(f.Sprintf(MoreSymbol + "« no function body »")) f.hasBody = false // No body return } // When entering function, always visit as block 0 block0 := NewBlock(f, fn.Blocks[0], 0) visitBasicBlock(fn.Blocks[0], infer, f, block0, &Loop{Parent: f}) f.hasBody = true } func visitBasicBlock(blk *ssa.BasicBlock, infer *TypeInfer, f *Function, bPrev *Block, l *Loop) { loopStateTransition(blk, infer, f, &l) if l.Bound == Static && l.HasNext() { infer.Logger.Printf(f.Sprintf(BlockSymbol+"%s %d (loop %s=%d)", fmtBlock("block"), blk.Index, l.CondVar.Name(), l.Index)) // Loop and can continue, so don't mark as visited yet } else { if _, ok := f.Visited[blk]; ok { infer.Logger.Printf(f.Sprintf(BlockSymbol+"%s %d (visited)", fmtBlock("block"), blk.Index)) f.Visited[blk]++ for i := 0; i < len(f.FuncDef.Params); i++ { for k, ea := range f.extraargs { if phi, ok := ea.(*ssa.Phi); ok { if bPrev.Index < len(phi.Edges) { for _, e := range phi.Edges { if f.FuncDef.Params[i].Caller.Name() == e.Name() { f.FuncDef.Params[i].Callee = phi // Remove from extra args if k < len(f.extraargs) { f.extraargs = append(f.extraargs[:k], f.extraargs[k+1:]...) } else { f.extraargs = f.extraargs[:k] } } } } } } } return } } infer.Logger.Printf(f.Sprintf(BlockSymbol+"%s %d; %s", fmtBlock("block"), blk.Index, fmtLoopHL(blk.Comment))) f.Visited[blk] = 0 for _, instr := range blk.Instrs { visitInstr(instr, infer, &Context{f, bPrev, l}) } } func visitInstr(instr ssa.Instruction, infer *TypeInfer, ctx *Context) { switch instr := instr.(type) { case *ssa.Alloc: visitAlloc(instr, infer, ctx) case *ssa.BinOp: visitBinOp(instr, infer, ctx) case *ssa.Call: visitCall(instr, infer, ctx) case *ssa.ChangeInterface: visitChangeInterface(instr, infer, ctx) case *ssa.ChangeType: visitChangeType(instr, infer, ctx) case *ssa.Convert: visitConvert(instr, infer, ctx) case *ssa.DebugRef: //infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"debug\t\t%s", instr)) case *ssa.Defer: visitDefer(instr, infer, ctx) case *ssa.Extract: visitExtract(instr, infer, ctx) case *ssa.FieldAddr: visitFieldAddr(instr, infer, ctx) case *ssa.Go: visitGo(instr, infer, ctx) case *ssa.If: visitIf(instr, infer, ctx) case *ssa.Index: visitIndex(instr, infer, ctx) case *ssa.IndexAddr: visitIndexAddr(instr, infer, ctx) case *ssa.Jump: visitJump(instr, infer, ctx) case *ssa.Lookup: visitLookup(instr, infer, ctx) case *ssa.MakeChan: visitMakeChan(instr, infer, ctx) case *ssa.MakeClosure: visitMakeClosure(instr, infer, ctx) case *ssa.MakeInterface: visitMakeInterface(instr, infer, ctx) case *ssa.MakeMap: visitMakeMap(instr, infer, ctx) case *ssa.MakeSlice: visitMakeSlice(instr, infer, ctx) case *ssa.MapUpdate: visitMapUpdate(instr, infer, ctx) case *ssa.Next: visitNext(instr, infer, ctx) case *ssa.Phi: visitPhi(instr, infer, ctx) case *ssa.Return: visitReturn(instr, infer, ctx) case *ssa.RunDefers: visitRunDefers(instr, infer, ctx) case *ssa.Send: visitSend(instr, infer, ctx) case *ssa.Select: visitSelect(instr, infer, ctx) case *ssa.Slice: visitSlice(instr, infer, ctx) case *ssa.Store: visitStore(instr, infer, ctx) case *ssa.TypeAssert: visitTypeAssert(instr, infer, ctx) case *ssa.UnOp: switch instr.Op { case token.ARROW: visitRecv(instr, infer, ctx) case token.MUL: visitDeref(instr, infer, ctx) default: visitSkip(instr, infer, ctx) } default: visitSkip(instr, infer, ctx) } } func visitAlloc(instr *ssa.Alloc, infer *TypeInfer, ctx *Context) { allocType := instr.Type().(*types.Pointer).Elem() switch t := allocType.Underlying().(type) { case *types.Array: // Static size array ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} if instr.Heap { ctx.F.Prog.arrays[ctx.F.locals[instr]] = make(Elems, t.Len()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc (array@heap) of type %s (%d elems)", ctx.F.locals[instr], instr.Type(), t.Len())) } else { ctx.F.arrays[ctx.F.locals[instr]] = make(Elems, t.Len()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc (array@local) of type %s (%d elems)", ctx.F.locals[instr], instr.Type(), t.Len())) } case *types.Struct: ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} if instr.Heap { ctx.F.Prog.structs[ctx.F.locals[instr]] = make(Fields, t.NumFields()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc (struct@heap) of type %s (%d fields)", ctx.F.locals[instr], instr.Type(), t.NumFields())) } else { ctx.F.structs[ctx.F.locals[instr]] = make(Fields, t.NumFields()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc (struct@local) of type %s (%d fields)", ctx.F.locals[instr], instr.Type(), t.NumFields())) } case *types.Pointer: switch pt := t.Elem().Underlying().(type) { case *types.Array: ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} if instr.Heap { ctx.F.Prog.arrays[ctx.F.locals[instr]] = make(Elems, pt.Len()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc/indirect (array@heap) of type %s (%d elems)", ctx.F.locals[instr], instr.Type(), pt.Len())) } else { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.arrays[ctx.F.locals[instr]] = make(Elems, pt.Len()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc/indirect (array@local) of type %s (%d elems)", ctx.F.locals[instr], instr.Type(), pt.Len())) } case *types.Struct: ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} if instr.Heap { ctx.F.Prog.structs[ctx.F.locals[instr]] = make(Fields, pt.NumFields()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc/indirect (struct@heap) of type %s (%d fields)", ctx.F.locals[instr], instr.Type(), pt.NumFields())) } else { ctx.F.structs[ctx.F.locals[instr]] = make(Fields, pt.NumFields()) infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc/indirect (struct@local) of type %s (%d fields)", ctx.F.locals[instr], instr.Type(), pt.NumFields())) } default: ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc/indirect of type %s", ctx.F.locals[instr], instr.Type().Underlying())) } default: ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = alloc of type %s", ctx.F.locals[instr], instr.Type().Underlying())) } } func visitBinOp(instr *ssa.BinOp, infer *TypeInfer, ctx *Context) { if ctx.L.State == Enter { switch ctx.L.Bound { case Unknown: switch instr.Op { case token.LSS: // i < N if i, ok := instr.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetCond(instr, i.Int64()-1) if _, ok := instr.X.(*ssa.Phi); ok && ctx.L.Start < ctx.L.End { ctx.L.Bound = Static infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i <= %s", fmtLoopHL(ctx.L.End))) return } ctx.L.Bound = Dynamic return } case token.LEQ: // i <= N if i, ok := instr.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetCond(instr, i.Int64()) if _, ok := instr.X.(*ssa.Phi); ok && ctx.L.Start < ctx.L.End { ctx.L.Bound = Static infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i <= %s", fmtLoopHL(ctx.L.End))) return } ctx.L.Bound = Dynamic return } case token.GTR: // i > N if i, ok := instr.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetCond(instr, i.Int64()+1) if _, ok := instr.X.(*ssa.Phi); ok && ctx.L.Start > ctx.L.End { ctx.L.Bound = Static infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i > %s", fmtLoopHL(ctx.L.End))) return } ctx.L.Bound = Dynamic return } case token.GEQ: // i >= N if i, ok := instr.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { ctx.L.SetCond(instr, i.Int64()) if _, ok := instr.X.(*ssa.Phi); ok && ctx.L.Start > ctx.L.End { ctx.L.Bound = Static infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"i >= %s", fmtLoopHL(ctx.L.End))) return } ctx.L.Bound = Dynamic return } } } } ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} visitSkip(instr, infer, ctx) } func visitCall(instr *ssa.Call, infer *TypeInfer, ctx *Context) { infer.Logger.Printf(ctx.F.Sprintf(CallSymbol+"%s = %s", instr.Name(), instr.String())) ctx.F.Call(instr, infer, ctx.B, ctx.L) } func visitChangeType(instr *ssa.ChangeType, infer *TypeInfer, ctx *Context) { inst, ok := ctx.F.locals[instr.X] if !ok { infer.Logger.Fatalf("changetype: %s: %v → %v", ErrUnknownValue, instr.X, instr) return } ctx.F.locals[instr] = inst if a, ok := ctx.F.arrays[ctx.F.locals[instr.X]]; ok { ctx.F.arrays[ctx.F.locals[instr]] = a } if s, ok := ctx.F.structs[ctx.F.locals[instr.X]]; ok { ctx.F.structs[ctx.F.locals[instr]] = s } if m, ok := ctx.F.maps[ctx.F.locals[instr.X]]; ok { ctx.F.maps[ctx.F.locals[instr]] = m } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s (type: %s ← %s)", instr.Name(), instr.X.Name(), fmtType(instr.Type()), fmtType(instr.X.Type().Underlying()))) ctx.F.revlookup[instr.Name()] = instr.X.Name() if indirect, ok := ctx.F.revlookup[instr.X.Name()]; ok { ctx.F.revlookup[instr.Name()] = indirect } return } func visitChangeInterface(instr *ssa.ChangeInterface, infer *TypeInfer, ctx *Context) { inst, ok := ctx.F.locals[instr.X] if !ok { infer.Logger.Fatalf("changeiface: %s: %v → %v", ErrUnknownValue, instr.X, instr) } ctx.F.locals[instr] = inst } func visitConvert(instr *ssa.Convert, infer *TypeInfer, ctx *Context) { _, ok := ctx.F.locals[instr.X] if !ok { if c, ok := instr.X.(*ssa.Const); ok { ctx.F.locals[instr.X] = &Const{c} } else if _, ok := instr.X.(*ssa.Global); ok { inst, ok := ctx.F.Prog.globals[instr.X] if !ok { infer.Logger.Fatalf("convert (global): %s: %+v", ErrUnknownValue, instr.X) } ctx.F.locals[instr.X] = inst infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s convert= %s (global)", ctx.F.locals[instr], instr.X.Name())) return } else { infer.Logger.Fatalf("convert: %s: %+v", ErrUnknownValue, instr.X) return } } ctx.F.locals[instr] = ctx.F.locals[instr.X] infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s convert= %s", ctx.F.locals[instr], instr.X.Name())) } func visitDefer(instr *ssa.Defer, infer *TypeInfer, ctx *Context) { ctx.F.defers = append(ctx.F.defers, instr) } func visitDeref(instr *ssa.UnOp, infer *TypeInfer, ctx *Context) { ptr, val := instr.X, instr // Globactx.L. if _, ok := ptr.(*ssa.Global); ok { inst, ok := ctx.F.Prog.globals[ptr] if !ok { infer.Logger.Fatalf("deref (global): %s: %+v", ErrUnknownValue, ptr) return } ctx.F.locals[ptr], ctx.F.locals[val] = inst, inst infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s deref= %s (global) of type %s", inst, ptr, ptr.Type())) // Initialise global array/struct if needed. initNestedRefVar(infer, ctx, ctx.F.locals[ptr], true) return } if basic, ok := derefType(ptr.Type()).Underlying().(*types.Basic); ok && basic.Kind() == types.Byte { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"deref: %+v is a byte", ptr)) // Create new byte instance here, bytes do no need explicit allocation. ctx.F.locals[ptr] = &Value{ptr, ctx.F.InstanceID(), ctx.L.Index} } // Locactx.L. inst, ok := ctx.F.locals[ptr] if !ok { infer.Logger.Fatalf("deref: %s: %+v", ErrUnknownValue, ptr) return } ctx.F.locals[ptr], ctx.F.locals[val] = inst, inst infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s deref= %s of type %s", val, ptr, ptr.Type())) // Initialise array/struct if needed. initNestedRefVar(infer, ctx, ctx.F.locals[ptr], false) return } func visitExtract(instr *ssa.Extract, infer *TypeInfer, ctx *Context) { if tupleInst, ok := ctx.F.locals[instr.Tuple]; ok { if _, ok := ctx.F.tuples[tupleInst]; !ok { // Tuple uninitialised infer.Logger.Fatalf("extract: %s: Unexpected tuple: %+v", ErrUnknownValue, instr) return } if inst := ctx.F.tuples[tupleInst][instr.Index]; inst == nil { ctx.F.tuples[tupleInst][instr.Index] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} } ctx.F.locals[instr] = ctx.F.tuples[tupleInst][instr.Index] initNestedRefVar(infer, ctx, ctx.F.locals[instr], false) // Detect select tuple. if _, ok := ctx.F.selects[tupleInst]; ok { switch instr.Index { case 0: ctx.F.selects[tupleInst].Index = ctx.F.locals[instr] infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = extract select{%d} (select-index) for %s", ctx.F.locals[instr], instr.Index, instr)) return default: infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = extract select{%d} for %s", ctx.F.locals[instr], instr.Index, instr)) return } } // Detect commaok tuple. if commaOk, ok := ctx.F.commaok[tupleInst]; ok { switch instr.Index { case 0: infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = extract commaOk{%d} for %s", ctx.F.locals[instr], instr.Index, instr)) return case 1: commaOk.OkCond = ctx.F.locals[instr] infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = extract commaOk{%d} (ok-test) for %s", ctx.F.locals[instr], instr.Index, instr)) return } } infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = tuple %s[%d] of %d", ctx.F.locals[instr], tupleInst, instr.Index, len(ctx.F.tuples[tupleInst]))) return } } func visitField(instr *ssa.Field, infer *TypeInfer, ctx *Context) { field, struc, index := instr, instr.X, instr.Field if sType, ok := struc.Type().Underlying().(*types.Struct); ok { sInst, ok := ctx.F.locals[struc] if !ok { infer.Logger.Fatalf("field: %s :%+v", ErrUnknownValue, struc) return } fields, ok := ctx.F.structs[sInst] if !ok { fields, ok = ctx.F.Prog.structs[sInst] if !ok { infer.Logger.Fatalf("field: %s: struct uninitialised %+v", ErrUnknownValue, sInst) return } } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s"+FieldSymbol+"{%d} of type %s", instr.Name(), sInst, index, sType.String())) if fields[index] != nil { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"accessed as %s", fields[index])) } else { fields[index] = &Value{field, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"field uninitialised, set to %s", field.Name())) } initNestedRefVar(infer, ctx, ctx.F.locals[field], false) ctx.F.locals[field] = fields[index] return } infer.Logger.Fatalf("field: %s: field is not struct: %+v", ErrInvalidVarRead, struc) } func visitFieldAddr(instr *ssa.FieldAddr, infer *TypeInfer, ctx *Context) { field, struc, index := instr, instr.X, instr.Field if sType, ok := derefType(struc.Type()).Underlying().(*types.Struct); ok { sInst, ok := ctx.F.locals[struc] if !ok { sInst, ok = ctx.F.Prog.globals[struc] if !ok { infer.Logger.Fatalf("field-addr: %s: %+v", ErrUnknownValue, struc) return } } // Check status of instance. switch inst := sInst.(type) { case *Value: // Continue case *External: // Continue infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"field-addr: %+v is external", sInst)) ctx.F.locals[field] = inst return case *Const: infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"field-addr: %+v is a constant", sInst)) if inst.Const.IsNil() { ctx.F.locals[field] = inst } return default: infer.Logger.Fatalf("field-addr: %s: not instance %+v", ErrUnknownValue, sInst) return } // Find the struct. fields, ok := ctx.F.structs[sInst] if !ok { fields, ok = ctx.F.Prog.structs[sInst] if !ok { infer.Logger.Fatalf("field-addr: %s: struct uninitialised %+v", ErrUnknownValue, sInst) return } } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s"+FieldSymbol+"{%d} of type %s", instr.Name(), sInst, index, sType.String())) if fields[index] != nil { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"accessed as %s", fields[index])) } else { fields[index] = &Value{field, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"field uninitialised, set to %s", field.Name())) } initNestedRefVar(infer, ctx, fields[index], false) ctx.F.locals[field] = fields[index] return } infer.Logger.Fatalf("field-addr: %s: field is not struct: %+v", ErrInvalidVarRead, struc) } func visitGo(instr *ssa.Go, infer *TypeInfer, ctx *Context) { infer.Logger.Printf(ctx.F.Sprintf(SpawnSymbol+"%s %s", fmtSpawn("spawn"), instr)) ctx.F.Go(instr, infer) } func visitIf(instr *ssa.If, infer *TypeInfer, ctx *Context) { if len(instr.Block().Succs) != 2 { infer.Logger.Fatal(ErrInvalidIfSucc) } // Detect and unroll ctx.L. if ctx.L.State != NonLoop && ctx.L.Bound == Static && instr.Cond == ctx.L.CondVar { if ctx.L.HasNext() { infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"loop continue %s", ctx.L)) visitBasicBlock(instr.Block().Succs[0], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[0], ctx.B.Index), ctx.L) } else { infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol+"loop exit %s", ctx.L)) visitBasicBlock(instr.Block().Succs[1], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[1], ctx.B.Index), ctx.L) } return } // Detect Select branches. if bin, ok := instr.Cond.(*ssa.BinOp); ok && bin.Op == token.EQL { for _, sel := range ctx.F.selects { if bin.X == sel.Index.(*Value).Value { if i, ok := bin.Y.(*ssa.Const); ok && i.Value.Kind() == constant.Int { //infer.Logger.Print(fmt.Sprintf("[select-%d]", i.Int64()), ctx.F.FuncDef.String()) parDef := ctx.F.FuncDef parDef.PutAway() // Save select visitBasicBlock(instr.Block().Succs[0], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[0], ctx.B.Index), ctx.L) ctx.F.FuncDef.PutAway() // Save case selCase, err := ctx.F.FuncDef.Restore() if err != nil { infer.Logger.Fatal("select-case:", err) } sel.MigoStmt.Cases[i.Int64()] = append(sel.MigoStmt.Cases[i.Int64()], selCase...) selParent, err := parDef.Restore() if err != nil { infer.Logger.Fatal("select-parent:", err) } parDef.AddStmts(selParent...) // Test if select has default branch & if this is default if !sel.Instr.Blocking && i.Int64() == int64(len(sel.Instr.States)-1) { infer.Logger.Print(ctx.F.Sprintf(SelectSymbol + "default")) if instr.Block().Succs[1].Comment == "select.done" { // Looks like it's empty infer.Logger.Printf(SplitSymbol+"Empty default branch (%d ⇾ %d)", instr.Block().Index, instr.Block().Succs[1].Index) selDefault := &migo.CallStatement{Name: fmt.Sprintf("%s#%d", ctx.F.Fn.String(), instr.Block().Succs[1].Index)} for i := 0; i < len(ctx.F.FuncDef.Params); i++ { for k, ea := range ctx.F.extraargs { if phi, ok := ea.(*ssa.Phi); ok { if instr.Block().Succs[1].Index < len(phi.Edges) { for _, e := range phi.Edges { if ctx.F.FuncDef.Params[i].Caller.Name() == e.Name() { ctx.F.FuncDef.Params[i].Callee = phi // Remove from extra args if k < len(ctx.F.extraargs) { ctx.F.extraargs = append(ctx.F.extraargs[:k], ctx.F.extraargs[k+1:]...) } else { ctx.F.extraargs = ctx.F.extraargs[:k] } } } } } } // This loop copies args from current function to Successor. if phi, ok := ctx.F.FuncDef.Params[i].Callee.(*ssa.Phi); ok { // Resolve in current scope if phi selDefault.AddParams(&migo.Parameter{Caller: phi.Edges[instr.Block().Succs[1].Index], Callee: ctx.F.FuncDef.Params[i].Callee}) } else { selDefault.AddParams(&migo.Parameter{Caller: ctx.F.FuncDef.Params[i].Callee, Callee: ctx.F.FuncDef.Params[i].Callee}) } } for _, ea := range ctx.F.extraargs { if phi, ok := ea.(*ssa.Phi); ok { selDefault.AddParams(&migo.Parameter{Caller: phi.Edges[instr.Block().Succs[1].Index], Callee: phi}) } else { selDefault.AddParams(&migo.Parameter{Caller: ea, Callee: ea}) } } parDef := ctx.F.FuncDef parDef.PutAway() // Save select sel.MigoStmt.Cases[len(sel.MigoStmt.Cases)-1] = append(sel.MigoStmt.Cases[len(sel.MigoStmt.Cases)-1], selDefault) selParent, err := parDef.Restore() if err != nil { infer.Logger.Fatal("select-parent:", err) } parDef.AddStmts(selParent...) } else { parDef := ctx.F.FuncDef parDef.PutAway() // Save select visitBasicBlock(instr.Block().Succs[1], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[1], ctx.B.Index), ctx.L) ctx.F.FuncDef.PutAway() // Save case selDefault, err := ctx.F.FuncDef.Restore() if err != nil { infer.Logger.Fatal("select-default:", err) } sel.MigoStmt.Cases[len(sel.MigoStmt.Cases)-1] = append(sel.MigoStmt.Cases[len(sel.MigoStmt.Cases)-1], selDefault...) selParent, err := parDef.Restore() if err != nil { infer.Logger.Fatal("select-parent:", err) } parDef.AddStmts(selParent...) } } else { infer.Logger.Printf(ctx.F.Sprintf(IfSymbol+"select-else "+JumpSymbol+"%d", instr.Block().Succs[1].Index)) visitBasicBlock(instr.Block().Succs[1], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[1], ctx.B.Index), ctx.L) } return // Select if-then-else handled } } } } var cond string if inst, ok := ctx.F.locals[instr.Cond]; ok && isCommaOk(ctx.F, inst) { cond = fmt.Sprintf("comma-ok %s", instr.Cond.Name()) } else { cond = fmt.Sprintf("%s", instr.Cond.Name()) } // Save parent. ctx.F.FuncDef.PutAway() infer.Logger.Printf(ctx.F.Sprintf(IfSymbol+"if %s then"+JumpSymbol+"%d", cond, instr.Block().Succs[0].Index)) visitBasicBlock(instr.Block().Succs[0], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[0], ctx.B.Index), ctx.L) // Save then. ctx.F.FuncDef.PutAway() infer.Logger.Printf(ctx.F.Sprintf(IfSymbol+"if %s else"+JumpSymbol+"%d", cond, instr.Block().Succs[1].Index)) if ctx.L.State == Body && ctx.L.LoopBlock == ctx.B.Index { // Infinite loop. infer.Logger.Printf(ctx.F.Sprintf(LoopSymbol + " infinite loop")) stmt := &migo.CallStatement{Name: fmt.Sprintf("%s#%d", ctx.F.Fn.String(), ctx.B.Index)} for _, p := range ctx.F.FuncDef.Params { stmt.AddParams(&migo.Parameter{Caller: p.Callee, Callee: p.Callee}) } ctx.F.FuncDef.AddStmts(stmt) } else { visitBasicBlock(instr.Block().Succs[1], infer, ctx.F, NewBlock(ctx.F, instr.Block().Succs[1], ctx.B.Index), ctx.L) } // Save else. ctx.F.FuncDef.PutAway() elseStmts, err := ctx.F.FuncDef.Restore() // Else if err != nil { infer.Logger.Fatal("restore else:", err) } thenStmts, err := ctx.F.FuncDef.Restore() // Then if err != nil { infer.Logger.Fatal("restore then:", err) } parentStmts, err := ctx.F.FuncDef.Restore() // Parent if err != nil { infer.Logger.Fatal("restore if-then-else parent:", err) } ctx.F.FuncDef.AddStmts(parentStmts...) ctx.F.FuncDef.AddStmts(&migo.IfStatement{Then: thenStmts, Else: elseStmts}) } func visitIndex(instr *ssa.Index, infer *TypeInfer, ctx *Context) { elem, array, index := instr, instr.X, instr.Index // Array. if aType, ok := array.Type().Underlying().(*types.Array); ok { aInst, ok := ctx.F.locals[array] if !ok { aInst, ok = ctx.F.Prog.globals[array] if !ok { infer.Logger.Fatalf("index: %s: array %+v", ErrUnknownValue, array) return } } elems, ok := ctx.F.arrays[aInst] if !ok { elems, ok = ctx.F.Prog.arrays[aInst] if !ok { infer.Logger.Fatalf("index: %s: not an array %+v", ErrUnknownValue, aInst) return } } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s"+FieldSymbol+"[%s] of type %s", instr.Name(), aInst, index, aType.String())) if elems[index] != nil { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"accessed as %s", elems[index])) } else { elems[index] = &Value{elem, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Printf(ctx.F.Sprintf(SubSymbol+"elem uninitialised, set to %s", elem.Name())) } initNestedRefVar(infer, ctx, elems[index], false) ctx.F.locals[elem] = elems[index] return } } func visitIndexAddr(instr *ssa.IndexAddr, infer *TypeInfer, ctx *Context) { elem, array, index := instr, instr.X, instr.Index // Array. if aType, ok := derefType(array.Type()).Underlying().(*types.Array); ok { aInst, ok := ctx.F.locals[array] if !ok { aInst, ok = ctx.F.Prog.globals[array] if !ok { infer.Logger.Fatalf("index-addr: %s: array %+v", ErrUnknownValue, array) return } } // Check status of instance. switch inst := aInst.(type) { case *Value: // Continue case *External: // External infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"index-addr: array %+v is external", aInst)) ctx.F.locals[elem] = inst case *Const: infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"index-addr: array %+v is a constant", aInst)) if inst.Const.IsNil() { ctx.F.locals[elem] = inst } return default: infer.Logger.Fatalf("index-addr: %s: array is not instance %+v", ErrUnknownValue, aInst) return } // Find the array. elems, ok := ctx.F.arrays[aInst] if !ok { elems, ok = ctx.F.Prog.arrays[aInst] if !ok { infer.Logger.Fatalf("index-addr: %s: array uninitialised %s", ErrUnknownValue, aInst) return } } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s"+FieldSymbol+"[%s] of type %s", instr.Name(), aInst, index, aType.String())) if elems[index] != nil { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"accessed as %s", elems[index])) } else { elems[index] = &Value{elem, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Printf(ctx.F.Sprintf(SubSymbol+"elem uninitialised, set to %s", elem.Name())) } initNestedRefVar(infer, ctx, elems[index], false) ctx.F.locals[elem] = elems[index] return } // Slices. if sType, ok := derefType(array.Type()).Underlying().(*types.Slice); ok { sInst, ok := ctx.F.locals[array] if !ok { sInst, ok = ctx.F.Prog.globals[array] if !ok { infer.Logger.Fatalf("index-addr: %s: slice %+v", ErrUnknownValue, array) return } } // Check status of instance. switch inst := sInst.(type) { case *Value: // Continue if basic, ok := sType.Elem().(*types.Basic); ok && basic.Kind() == types.Byte { ctx.F.locals[elem] = inst return } case *External: // External infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"index-addr: slice %+v is external", sInst)) ctx.F.locals[elem] = inst return case *Const: infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"index-addr: slice %+v is a constant", sInst)) if inst.Const.IsNil() { ctx.F.locals[elem] = inst } return default: infer.Logger.Fatalf("index-addr: %s: slice is not instance %+v", ErrUnknownValue, sInst) return } // Find the slice. elems, ok := ctx.F.arrays[sInst] if !ok { elems, ok = ctx.F.Prog.arrays[sInst] if !ok { infer.Logger.Fatalf("index-addr: %s: slice uninitialised %+v", ErrUnknownValue, sInst) return } } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s"+FieldSymbol+"[%s] (slice) of type %s", instr.Name(), sInst, index, sType.String())) if elems[index] != nil { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"accessed as %s", elems[index])) } else { elems[index] = &Value{elem, ctx.F.InstanceID(), ctx.L.Index} infer.Logger.Printf(ctx.F.Sprintf(SubSymbol+"elem uninitialised, set to %s", elem.Name())) } initNestedRefVar(infer, ctx, elems[index], false) ctx.F.locals[elem] = elems[index] return } infer.Logger.Fatalf("index-addr: %s: not array/slice %+v", ErrInvalidVarRead, array) } func visitJump(jump *ssa.Jump, infer *TypeInfer, ctx *Context) { if len(jump.Block().Succs) != 1 { infer.Logger.Fatal(ErrInvalidJumpSucc) } curr, next := jump.Block(), jump.Block().Succs[0] infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"block %d%s%d", curr.Index, fmtLoopHL(JumpSymbol), next.Index)) switch ctx.L.State { case Exit: ctx.L.State = NonLoop } if len(next.Preds) > 1 { infer.Logger.Printf(ctx.F.Sprintf(SplitSymbol+"Jump (%d ⇾ %d) %s", curr.Index, next.Index, ctx.L.String())) var stmt *migo.CallStatement //if ctx.L.Bound == Static && ctx.L.HasNext() { //stmt = &migo.CallStatement{Name: fmt.Sprintf("%s#%d_loop%d", ctx.F.Fn.String(), next.Index, ctx.L.Index), Params: []*migo.Parameter{}} //} else { stmt = &migo.CallStatement{Name: fmt.Sprintf("%s#%d", ctx.F.Fn.String(), next.Index)} for i := 0; i < len(ctx.F.FuncDef.Params); i++ { for k, ea := range ctx.F.extraargs { if phi, ok := ea.(*ssa.Phi); ok { if jump.Block().Index < len(phi.Edges) { for _, e := range phi.Edges { if ctx.F.FuncDef.Params[i].Caller.Name() == e.Name() { ctx.F.FuncDef.Params[i].Callee = phi // Remove from extra args if k < len(ctx.F.extraargs) { ctx.F.extraargs = append(ctx.F.extraargs[:k], ctx.F.extraargs[k+1:]...) } else { ctx.F.extraargs = ctx.F.extraargs[:k] } } } } } } // This loop copies args from current function to Successor. if phi, ok := ctx.F.FuncDef.Params[i].Callee.(*ssa.Phi); ok { // Resolve in current scope if phi stmt.AddParams(&migo.Parameter{Caller: phi.Edges[jump.Block().Index], Callee: ctx.F.FuncDef.Params[i].Callee}) } else { stmt.AddParams(&migo.Parameter{Caller: ctx.F.FuncDef.Params[i].Callee, Callee: ctx.F.FuncDef.Params[i].Callee}) } } for _, ea := range ctx.F.extraargs { if phi, ok := ea.(*ssa.Phi); ok { stmt.AddParams(&migo.Parameter{Caller: phi.Edges[jump.Block().Index], Callee: phi}) } else { stmt.AddParams(&migo.Parameter{Caller: ea, Callee: ea}) } } //} ctx.F.FuncDef.AddStmts(stmt) if _, visited := ctx.F.Visited[next]; !visited { newBlock := NewBlock(ctx.F, next, ctx.B.Index) oldFunc, newFunc := ctx.F.FuncDef, newBlock.MigoDef if ctx.L.Bound == Static && ctx.L.HasNext() { newFunc = migo.NewFunction(fmt.Sprintf("%s#%d_loop%d", ctx.F.Fn.String(), next.Index, ctx.L.Index)) } for _, p := range stmt.Params { newFunc.AddParams(&migo.Parameter{Caller: p.Callee, Callee: p.Callee}) } ctx.F.FuncDef = newFunc infer.Env.MigoProg.AddFunction(newFunc) visitBasicBlock(next, infer, ctx.F, newBlock, ctx.L) ctx.F.FuncDef = oldFunc return } } visitBasicBlock(next, infer, ctx.F, NewBlock(ctx.F, next, ctx.B.Index), ctx.L) } func visitLookup(instr *ssa.Lookup, infer *TypeInfer, ctx *Context) { v, ok := ctx.F.locals[instr.X] if !ok { if c, ok := instr.X.(*ssa.Const); ok { ctx.F.locals[instr.X] = &Const{c} v = ctx.F.locals[instr.X] } else { infer.Logger.Fatalf("lookup: %s: %+v", ErrUnknownValue, instr.X) return } } // Lookup test. idx, ok := ctx.F.locals[instr.Index] if !ok { if c, ok := instr.Index.(*ssa.Const); ok { idx = &Const{c} } else { idx = &Value{instr.Index, ctx.F.InstanceID(), ctx.L.Index} } ctx.F.locals[instr.Index] = idx } ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} initNestedRefVar(infer, ctx, ctx.F.locals[instr], false) if instr.CommaOk { ctx.F.commaok[ctx.F.locals[instr]] = &CommaOk{Instr: instr, Result: ctx.F.locals[instr]} ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 2) // { elem, lookupOk } } infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = lookup %s[%s]", ctx.F.locals[instr], v, idx)) } func visitMakeChan(instr *ssa.MakeChan, infer *TypeInfer, ctx *Context) { newch := &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.locals[instr] = newch chType, ok := instr.Type().(*types.Chan) if !ok { infer.Logger.Fatal(ErrMakeChanNonChan) } bufSz, ok := instr.Size.(*ssa.Const) if !ok { infer.Logger.Fatal(ErrNonConstChanBuf) } infer.Logger.Printf(ctx.F.Sprintf(ChanSymbol+"%s = %s {t:%s, buf:%d} @ %s", newch, fmtChan("chan"), chType.Elem(), bufSz.Int64(), fmtPos(infer.SSA.FSet.Position(instr.Pos()).String()))) ctx.F.FuncDef.AddStmts(&migo.NewChanStatement{Name: instr, Chan: newch.String(), Size: bufSz.Int64()}) // Make sure it is not a duplicated extraargs var found bool for _, ea := range ctx.F.extraargs { if phi, ok := ea.(*ssa.Phi); ok { if instr.Block().Index < len(phi.Edges) { if phi.Edges[instr.Block().Index].Name() == instr.Name() { found = true } } } } if !found { ctx.F.extraargs = append(ctx.F.extraargs, instr) } } func visitMakeClosure(instr *ssa.MakeClosure, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.Prog.closures[ctx.F.locals[instr]] = make(Captures, len(instr.Bindings)) for i, binding := range instr.Bindings { ctx.F.Prog.closures[ctx.F.locals[instr]][i] = ctx.F.locals[binding] } infer.Logger.Print(ctx.F.Sprintf(NewSymbol+"%s = make closure", ctx.F.locals[instr])) } func visitMakeInterface(instr *ssa.MakeInterface, infer *TypeInfer, ctx *Context) { iface, ok := ctx.F.locals[instr.X] if !ok { if c, ok := instr.X.(*ssa.Const); ok { ctx.F.locals[instr.X] = &Const{c} } else { infer.Logger.Fatalf("make-iface: %s: %s", ErrUnknownValue, instr.X) return } } ctx.F.locals[instr] = iface infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = make-iface %s", ctx.F.locals[instr], instr.String())) } func visitMakeMap(instr *ssa.MakeMap, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.maps[ctx.F.locals[instr]] = make(map[Instance]Instance) infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = make-map", ctx.F.locals[instr])) } func visitMakeSlice(instr *ssa.MakeSlice, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.arrays[ctx.F.locals[instr]] = make(Elems) infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = make-slice", ctx.F.locals[instr])) } func visitMapUpdate(instr *ssa.MapUpdate, infer *TypeInfer, ctx *Context) { inst, ok := ctx.F.locals[instr.Map] if !ok { infer.Logger.Fatalf("map-update: %s: %s", ErrUnknownValue, instr.Map) return } m, ok := ctx.F.maps[inst] if !ok { ctx.F.maps[inst] = make(map[Instance]Instance) // XXX This shouldn't happen m = ctx.F.maps[inst] // The map must be defined somewhere we skipped infer.Logger.Printf("map-update: uninitialised map: %+v %s", instr.Map, instr.Map.String()) } k, ok := ctx.F.locals[instr.Key] if !ok { k = &Value{instr.Key, ctx.F.InstanceID(), ctx.L.Index} ctx.F.locals[instr.Key] = k } v, ok := ctx.F.locals[instr.Value] if !ok { if c, ok := instr.Value.(*ssa.Const); ok { v = &Const{c} } else { v = &Value{instr.Value, ctx.F.InstanceID(), ctx.L.Index} } ctx.F.locals[instr.Value] = v } m[k] = v infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"%s[%s] = %s", inst, k, v)) } func visitNext(instr *ssa.Next, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 3) // { ok, k, v} infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s (ok, k, v) = next", ctx.F.locals[instr])) } func visitPhi(instr *ssa.Phi, infer *TypeInfer, ctx *Context) { loopDetectBounds(instr, infer, ctx) if _, ok := instr.Type().(*types.Chan); ok { // Replace existing non-edge extra args with the same name. for _, e := range instr.Edges { EALOOP: for i, ea := range ctx.F.extraargs { if e.Name() == ea.Name() { ctx.F.extraargs = append(ctx.F.extraargs[:i], ctx.F.extraargs[i+1:]...) break EALOOP } } } ctx.F.extraargs = append(ctx.F.extraargs, instr) } } func visitRecv(instr *ssa.UnOp, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} // received value ch, ok := ctx.F.locals[instr.X] if !ok { // Channel does not exist infer.Logger.Fatalf("recv: %s: %+v", ErrUnknownValue, instr.X) return } // Receive test. if instr.CommaOk { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.commaok[ctx.F.locals[instr]] = &CommaOk{Instr: instr, Result: ctx.F.locals[instr]} ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 2) // { recvVal, recvOk } } pos := infer.SSA.DecodePos(ch.(*Value).Pos()) infer.Logger.Print(ctx.F.Sprintf(RecvSymbol+"%s = %s @ %s", ctx.F.locals[instr], ch, fmtPos(pos))) if paramName, ok := ctx.F.revlookup[ch.String()]; ok { ctx.F.FuncDef.AddStmts(&migo.RecvStatement{Chan: paramName}) } else { if _, ok := instr.X.(*ssa.Phi); ok { // if it's a phi, selection is made in the parameter ctx.F.FuncDef.AddStmts(&migo.RecvStatement{Chan: instr.X.Name()}) } else { ctx.F.FuncDef.AddStmts(&migo.RecvStatement{Chan: ch.(*Value).Name()}) } } // Initialise received value if needed. initNestedRefVar(infer, ctx, ctx.F.locals[instr], false) } func visitReturn(ret *ssa.Return, infer *TypeInfer, ctx *Context) { switch len(ret.Results) { case 0: infer.Logger.Printf(ctx.F.Sprintf(ReturnSymbol)) case 1: if c, ok := ret.Results[0].(*ssa.Const); ok { ctx.F.locals[ret.Results[0]] = &Const{c} } res, ok := ctx.F.locals[ret.Results[0]] if !ok { infer.Logger.Printf("Returning uninitialised value %s/%s", ret.Results[0].Name(), ctx.F.locals[ret.Results[0]]) return } ctx.F.retvals = append(ctx.F.retvals, ctx.F.locals[ret.Results[0]]) infer.Logger.Printf(ctx.F.Sprintf(ReturnSymbol+"return[1] %s %v", res, ctx.F.retvals)) default: for _, res := range ret.Results { ctx.F.retvals = append(ctx.F.retvals, ctx.F.locals[res]) } infer.Logger.Printf(ctx.F.Sprintf(ReturnSymbol+"return[%d] %v", len(ret.Results), ctx.F.retvals)) } } func visitRunDefers(instr *ssa.RunDefers, infer *TypeInfer, ctx *Context) { for i := len(ctx.F.defers) - 1; i >= 0; i-- { common := ctx.F.defers[i].Common() if common.StaticCallee() != nil { callee := ctx.F.prepareCallFn(common, common.StaticCallee(), nil) visitFunc(callee.Fn, infer, callee) if callee.HasBody() { callStmt := &migo.CallStatement{Name: callee.Fn.String(), Params: []*migo.Parameter{}} for _, c := range common.Args { if _, ok := c.Type().(*types.Chan); ok { infer.Logger.Fatalf("channel in defer: %s", ErrUnimplemented) } } callee.FuncDef.AddStmts(callStmt) } } } } func visitSelect(instr *ssa.Select, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.selects[ctx.F.locals[instr]] = &Select{ Instr: instr, MigoStmt: &migo.SelectStatement{Cases: [][]migo.Statement{}}, } selStmt := ctx.F.selects[ctx.F.locals[instr]].MigoStmt for _, sel := range instr.States { ch, ok := ctx.F.locals[sel.Chan] if !ok { infer.Logger.Print("Select found an unknown channel", sel.Chan.String()) } var stmt migo.Statement //c := getChan(ch.Var(), infer) switch sel.Dir { case types.SendOnly: if paramName, ok := ctx.F.revlookup[ch.String()]; ok { stmt = &migo.SendStatement{Chan: paramName} } else { if _, ok := sel.Chan.(*ssa.Phi); ok { // if it's a phi, selection is made in the parameter stmt = &migo.SendStatement{Chan: sel.Chan.Name()} } else { stmt = &migo.SendStatement{Chan: ch.(*Value).Name()} } } case types.RecvOnly: if paramName, ok := ctx.F.revlookup[ch.String()]; ok { stmt = &migo.RecvStatement{Chan: paramName} } else { if _, ok := ch.(*Value); ok { if _, ok := sel.Chan.(*ssa.Phi); ok { // if it's a phi, selection is made in the parameter stmt = &migo.RecvStatement{Chan: sel.Chan.Name()} } else { stmt = &migo.RecvStatement{Chan: ch.(*Value).Name()} } } else { // Warning: receiving from external channels (e.g. cgo) // will cause problems stmt = &migo.TauStatement{} } } } selStmt.Cases = append(selStmt.Cases, []migo.Statement{stmt}) } // Default case exists. if !instr.Blocking { selStmt.Cases = append(selStmt.Cases, []migo.Statement{&migo.TauStatement{}}) } ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 2+len(selStmt.Cases)) // index + recvok + cases ctx.F.FuncDef.AddStmts(selStmt) infer.Logger.Print(ctx.F.Sprintf(SelectSymbol+" %d cases %s = %s", 2+len(selStmt.Cases), instr.Name(), instr.String())) } func visitSend(instr *ssa.Send, infer *TypeInfer, ctx *Context) { ch, ok := ctx.F.locals[instr.Chan] if !ok { infer.Logger.Fatalf("send: %s: %+v", ErrUnknownValue, instr.Chan) } pos := infer.SSA.DecodePos(ch.(*Value).Pos()) infer.Logger.Printf(ctx.F.Sprintf(SendSymbol+"%s @ %s", ch, fmtPos(pos))) if paramName, ok := ctx.F.revlookup[ch.String()]; ok { ctx.F.FuncDef.AddStmts(&migo.SendStatement{Chan: paramName}) } else { if _, ok := instr.Chan.(*ssa.Phi); ok { ctx.F.FuncDef.AddStmts(&migo.SendStatement{Chan: instr.Chan.Name()}) } else { ctx.F.FuncDef.AddStmts(&migo.SendStatement{Chan: ch.(*Value).Name()}) } } } func visitSkip(instr ssa.Instruction, infer *TypeInfer, ctx *Context) { if v, isVal := instr.(ssa.Value); isVal { infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"%T\t%s = %s", v, v.Name(), v.String())) return } infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"%T\t%s", instr, instr)) } func visitSlice(instr *ssa.Slice, infer *TypeInfer, ctx *Context) { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} if _, ok := ctx.F.locals[instr.X]; !ok { infer.Logger.Fatalf("slice: %s: %+v", ErrUnknownValue, instr.X) return } if basic, ok := instr.Type().Underlying().(*types.Basic); ok && basic.Kind() == types.String { infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"%s = slice on string, skipping", ctx.F.locals[instr])) return } if slice, ok := instr.Type().Underlying().(*types.Slice); ok { if basic, ok := slice.Elem().Underlying().(*types.Basic); ok && basic.Kind() == types.Byte { infer.Logger.Printf(ctx.F.Sprintf(SkipSymbol+"%s = slice on byte, skipping", ctx.F.locals[instr])) return } } aInst, ok := ctx.F.arrays[ctx.F.locals[instr.X]] if !ok { aInst, ok = ctx.F.Prog.arrays[ctx.F.locals[instr.X]] if !ok { switch ctx.F.locals[instr.X].(type) { case *Value: // Continue infer.Logger.Fatalf("slice: %s: non-slice %+v", ErrUnknownValue, instr.X) return case *Const: ctx.F.arrays[ctx.F.locals[instr.X]] = make(Elems) aInst = ctx.F.arrays[ctx.F.locals[instr.X]] infer.Logger.Print(ctx.F.Sprintf("slice: const %s %s", instr.X.Name(), ctx.F.locals[instr.X])) return } } ctx.F.Prog.arrays[ctx.F.locals[instr]] = aInst infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = slice %s", ctx.F.locals[instr], ctx.F.locals[instr.X])) return } ctx.F.arrays[ctx.F.locals[instr]] = aInst infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = slice %s", ctx.F.locals[instr], ctx.F.locals[instr.X])) } func visitStore(instr *ssa.Store, infer *TypeInfer, ctx *Context) { source, dstPtr := instr.Val, instr.Addr // Globactx.L. if _, ok := dstPtr.(*ssa.Global); ok { dstInst, ok := ctx.F.Prog.globals[dstPtr] if !ok { infer.Logger.Fatalf("store (global): %s: %+v", ErrUnknownValue, dstPtr) } inst, ok := ctx.F.locals[source] if !ok { inst, ok = ctx.F.Prog.globals[source] if !ok { if c, ok := source.(*ssa.Const); ok { inst = &Const{c} } else { infer.Logger.Fatalf("store (global): %s: %+v", ErrUnknownValue, source) } } } ctx.F.Prog.globals[dstPtr] = inst switch source.Type().Underlying().(type) { case *types.Array: ctx.F.updateInstances(dstInst, inst) case *types.Slice: ctx.F.updateInstances(dstInst, inst) case *types.Struct: ctx.F.updateInstances(dstInst, inst) case *types.Map: ctx.F.updateInstances(dstInst, inst) default: // Nothing to update. } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"%s = %s (global)", dstPtr.Name(), ctx.F.locals[source])) return } if basic, ok := derefType(dstPtr.Type()).Underlying().(*types.Basic); ok && basic.Kind() == types.Byte { infer.Logger.Print(ctx.F.Sprintf(SubSymbol+"store: %+v is a byte", dstPtr)) ctx.F.locals[dstPtr] = &Value{dstPtr, ctx.F.InstanceID(), ctx.L.Index} } // Locactx.L. dstInst, ok := ctx.F.locals[dstPtr] if !ok { infer.Logger.Fatalf("store: addr %s: %+v", ErrUnknownValue, dstPtr) } inst, ok := ctx.F.locals[source] if !ok { if c, ok := source.(*ssa.Const); ok { inst = &Const{c} } else { infer.Logger.Printf("store: val %s%s: %s", source.Name(), source.Type(), ErrUnknownValue) } } ctx.F.locals[dstPtr] = inst switch source.Type().Underlying().(type) { case *types.Array: ctx.F.updateInstances(dstInst, inst) case *types.Slice: ctx.F.updateInstances(dstInst, inst) case *types.Struct: ctx.F.updateInstances(dstInst, inst) case *types.Map: ctx.F.updateInstances(dstInst, inst) default: // Nothing to update. } infer.Logger.Print(ctx.F.Sprintf(ValSymbol+"*%s store= %s/%s", dstPtr.Name(), source.Name(), ctx.F.locals[source])) return } func visitTypeAssert(instr *ssa.TypeAssert, infer *TypeInfer, ctx *Context) { if iface, ok := instr.AssertedType.(*types.Interface); ok { if meth, _ := types.MissingMethod(instr.X.Type(), iface, true); meth == nil { // No missing methods inst, ok := ctx.F.locals[instr.X] if !ok { infer.Logger.Fatalf("typeassert: %s: iface X %+v", ErrUnknownValue, instr.X.Name()) return } if instr.CommaOk { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.commaok[ctx.F.locals[instr]] = &CommaOk{Instr: instr, Result: ctx.F.locals[instr]} ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 2) infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = typeassert iface %s commaok", ctx.F.locals[instr], inst)) return } ctx.F.locals[instr] = inst infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = typeassert iface %s", ctx.F.locals[instr], inst)) return } infer.Logger.Fatalf("typeassert: %s: %+v", ErrMethodNotFound, instr) return } inst, ok := ctx.F.locals[instr.X] if !ok { infer.Logger.Fatalf("typeassert: %s: assert from %+v", ErrUnknownValue, instr.X) return } if instr.CommaOk { ctx.F.locals[instr] = &Value{instr, ctx.F.InstanceID(), ctx.L.Index} ctx.F.commaok[ctx.F.locals[instr]] = &CommaOk{Instr: instr, Result: ctx.F.locals[instr]} ctx.F.tuples[ctx.F.locals[instr]] = make(Tuples, 2) infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = typeassert %s commaok", ctx.F.locals[instr], inst)) return } ctx.F.locals[instr] = inst infer.Logger.Print(ctx.F.Sprintf(SkipSymbol+"%s = typeassert %s", ctx.F.locals[instr], ctx.F.locals[instr.X])) return //infer.Logger.Fatalf("typeassert: %s: %+v", ErrIncompatType, instr) } ================================================ FILE: ssabuilder/callgraph/callgraph.go ================================================ // Package callgraph represents function call graph. // package callgraph // import "github.com/nickng/dingo-hunter/ssabuilder/callgraph" import ( "golang.org/x/tools/go/ssa" ) type Node struct { Func *ssa.Function Children []*Node } var ( visitedFunc map[*ssa.Function]bool visitedBlock map[*ssa.BasicBlock]bool ) func Build(main *ssa.Function) *Node { root := &Node{ Func: main, Children: []*Node{}, } visitedFunc = make(map[*ssa.Function]bool) visitedBlock = make(map[*ssa.BasicBlock]bool) visitedFunc[root.Func] = true visitBlock(root.Func.Blocks[0], root) return root } // FuncVisitor is an interface for analysing callgraph with a 'visit' function. type FuncVisitor interface { Visit(f *ssa.Function) } // Traverse callgraph in depth-first order. func (node *Node) Traverse(v FuncVisitor) { v.Visit(node.Func) for _, c := range node.Children { c.Traverse(v) } } func visitBlock(b *ssa.BasicBlock, node *Node) { if _, ok := visitedBlock[b]; ok { return } visitedBlock[b] = true for _, instr := range b.Instrs { switch instr := instr.(type) { case *ssa.Call: if f := instr.Common().StaticCallee(); f != nil { if _, ok := visitedFunc[f]; !ok { visitedFunc[f] = true node.Children = append(node.Children, &Node{ Func: f, Children: []*Node{}, }) } } case *ssa.Go: if f := instr.Common().StaticCallee(); f != nil { if _, ok := visitedFunc[f]; !ok { visitedFunc[f] = true node.Children = append(node.Children, &Node{ Func: f, Children: []*Node{}, }) } } case *ssa.If: visitBlock(instr.Block().Succs[0], node) visitBlock(instr.Block().Succs[1], node) case *ssa.Jump: // End of a block visitBlock(instr.Block().Succs[0], node) case *ssa.Return: // End of a function for _, child := range node.Children { if _, ok := visitedFunc[child.Func]; !ok { visitedFunc[child.Func] = true visitBlock(child.Func.Blocks[0], child) } } } } } ================================================ FILE: ssabuilder/channel.go ================================================ package ssabuilder // Channel helper functions. // Most of the functions in this file are modified from golan.org/x/tools/oracle import ( "go/token" "go/types" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" ) type ChanOpType int const ( ChanMake ChanOpType = iota ChanSend ChanRecv ChanClose ) // ChanOp abstracts an ssa.Send, ssa.Unop(ARROW) or a SelectState. type ChanOp struct { Value ssa.Value Type ChanOpType Pos token.Pos } // chanOps extract all channel operations from an instruction. func chanOps(instr ssa.Instruction) []ChanOp { var ops []ChanOp switch instr := instr.(type) { case *ssa.Send: ops = append(ops, ChanOp{instr.Chan, ChanSend, instr.Pos()}) case *ssa.UnOp: if instr.Op == token.ARROW { ops = append(ops, ChanOp{instr.X, ChanRecv, instr.Pos()}) } case *ssa.Select: for _, st := range instr.States { switch st.Dir { case types.SendOnly: ops = append(ops, ChanOp{st.Chan, ChanSend, st.Pos}) case types.RecvOnly: ops = append(ops, ChanOp{st.Chan, ChanRecv, st.Pos}) } } case ssa.CallInstruction: common := instr.Common() if b, ok := common.Value.(*ssa.Builtin); ok && b.Name() == "close" { ops = append(ops, ChanOp{common.Args[0], ChanClose, common.Pos()}) } } return ops } // progChanOps extract all channels from a program. func progChanOps(prog *ssa.Program) []ChanOp { var ops []ChanOp // all sends/receives of opposite direction // Look at all channel operations in the whole ssa.Program. allFuncs := ssautil.AllFunctions(prog) for fn := range allFuncs { for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range chanOps(instr) { ops = append(ops, op) } } } } return ops } // purgeChanOps removes channels that are of different type as queryOp, i.e. // channel we are looking for. func purgeChanOps(ops []ChanOp, ch ssa.Value) []ChanOp { i := 0 for _, op := range ops { if types.Identical(op.Value.Type().Underlying().(*types.Chan).Elem(), ch.Type().Underlying().(*types.Chan).Elem()) { ops[i] = op i++ } } ops = ops[:i] return ops } ================================================ FILE: ssabuilder/errors.go ================================================ package ssabuilder import "errors" var ErrPtaInternal = errors.New("internal error: pointer analysis failed") ================================================ FILE: ssabuilder/pointer.go ================================================ package ssabuilder // Pointer analysis helper functions. // Most of the functions in this file are modified from golan.org/x/tools/oracle import ( "fmt" "io" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" ) // Create a pointer.Config whose scope is the initial packages of lprog // and their dependencies. func setupPTA(prog *ssa.Program, lprog *loader.Program, ptaLog io.Writer) (*pointer.Config, error) { // TODO(adonovan): the body of this function is essentially // duplicated in all go/pointer clients. Refactor. // For each initial package (specified on the command line), // if it has a main function, analyze that, // otherwise analyze its tests, if any. var testPkgs, mains []*ssa.Package for _, info := range lprog.InitialPackages() { initialPkg := prog.Package(info.Pkg) // Add package to the pointer analysis scope. if initialPkg.Func("main") != nil { mains = append(mains, initialPkg) } else { testPkgs = append(testPkgs, initialPkg) } } if testPkgs != nil { for _, testPkg := range testPkgs { if p := prog.CreateTestMainPackage(testPkg); p != nil { mains = append(mains, p) } } } if mains == nil { return nil, fmt.Errorf("analysis scope has no main and no tests") } return &pointer.Config{ Log: ptaLog, Mains: mains, Reflection: false, // We don't consider reflection in our analysis. }, nil } ================================================ FILE: ssabuilder/print.go ================================================ package ssabuilder import ( "io" "sort" "github.com/nickng/dingo-hunter/ssabuilder/callgraph" "golang.org/x/tools/go/ssa" ) type Members []ssa.Member func (m Members) Len() int { return len(m) } func (m Members) Less(i, j int) bool { return m[i].Pos() < m[j].Pos() } func (m Members) Swap(i, j int) { m[i], m[j] = m[j], m[i] } // WriteTo writes SSA IR of info.Prog to w. func (info *SSAInfo) WriteTo(w io.Writer) (int64, error) { visitedFunc := make(map[*ssa.Function]bool) var n int64 nodeQueue := []*callgraph.Node{info.CallGraph()} for len(nodeQueue) > 0 { head := nodeQueue[0] headFunc := head.Func nodeQueue = nodeQueue[1:] if _, ok := visitedFunc[headFunc]; !ok { visitedFunc[headFunc] = true written, err := headFunc.WriteTo(w) if err != nil { return n, err } n += written } for _, childNode := range head.Children { nodeQueue = append(nodeQueue, childNode) } } return n, nil } // WriteAll writes all SSA IR to w. func (info *SSAInfo) WriteAll(w io.Writer) (int64, error) { pkgFuncs := make(map[*ssa.Package]Members) var n int64 for _, pkg := range info.Prog.AllPackages() { pkgFuncs[pkg] = make(Members, 0) for _, memb := range pkg.Members { if f, ok := memb.(*ssa.Function); ok { pkgFuncs[pkg] = append(pkgFuncs[pkg], f) } } sort.Sort(pkgFuncs[pkg]) } for pkg, funcs := range pkgFuncs { for _, f := range funcs { written, err := pkg.Func(f.Name()).WriteTo(w) if err != nil { return n, err } n += written } } return n, nil } ================================================ FILE: ssabuilder/ssabuild.go ================================================ // Package ssabuilder provides a wrapper for building SSA IR from Go source code. // package ssabuilder // import "github.com/nickng/dingo-hunter/ssabuilder" import ( "fmt" "go/build" "go/token" "io" "io/ioutil" "log" "golang.org/x/tools/go/loader" "golang.org/x/tools/go/pointer" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" "github.com/nickng/dingo-hunter/ssabuilder/callgraph" ) // A Mode value is a flag indicating how the source code are supplied. type Mode uint const ( // FromFiles is option to use a list of filenames for initial packages. FromFiles Mode = 1 << iota // FromString is option to use a string as body of initial package. FromString ) // Config holds the configuration for building SSA IR. type Config struct { BuildMode Mode Files []string // (Initial) files to load. Source string // Source code. BuildLog io.Writer // Build log. PtaLog io.Writer // Pointer analysis log. LogFlags int // Flags for build/pta log. BadPkgs map[string]string // Packages not to load (with reasons). } // SSAInfo is the SSA IR + metainfo built from a given Config. type SSAInfo struct { BuildConf *Config // Build configuration (initial files, logs). IgnoredPkgs []string // Packages not loaded (respects BuildConf.BadPkgs). FSet *token.FileSet // FileSet for parsed source files. Prog *ssa.Program // SSA IR for whole program. PtaConf *pointer.Config // Pointer analysis config. Logger *log.Logger // Build logger. } var ( // Packages that should not be loaded (and reasons) by default badPkgs = map[string]string{ "fmt": "Recursive calls unrelated to communication", "reflect": "Reflection not supported for static analyser", "runtime": "Runtime contains threads that are not user related", "strings": "Strings function does not have communication", "sync": "Atomics confuse analyser", "time": "Time not supported", "rand": "Math does not use channels", } ) // NewConfig creates a new default build configuration. func NewConfig(files []string) (*Config, error) { if len(files) == 0 { return nil, fmt.Errorf("no files specified or analysis") } return &Config{ BuildMode: FromFiles, Files: files, BuildLog: ioutil.Discard, PtaLog: ioutil.Discard, LogFlags: log.LstdFlags, BadPkgs: badPkgs, }, nil } // NewConfigFromString creates a new default build configuration. func NewConfigFromString(s string) (*Config, error) { return &Config{ BuildMode: FromString, Source: s, BuildLog: ioutil.Discard, PtaLog: ioutil.Discard, LogFlags: log.LstdFlags, BadPkgs: badPkgs, }, nil } // Build constructs the SSA IR using given config, and sets up pointer analysis. func (conf *Config) Build() (*SSAInfo, error) { var lconf = loader.Config{Build: &build.Default} buildLog := log.New(conf.BuildLog, "ssabuild: ", conf.LogFlags) if conf.BuildMode == FromFiles { args, err := lconf.FromArgs(conf.Files, false /* No tests */) if err != nil { return nil, err } if len(args) > 0 { return nil, fmt.Errorf("surplus arguments: %q", args) } } else if conf.BuildMode == FromString { f, err := lconf.ParseFile("", conf.Source) if err != nil { return nil, err } lconf.CreateFromFiles("", f) } else { buildLog.Fatal("Unknown build mode") } // Load, parse and type-check program lprog, err := lconf.Load() if err != nil { return nil, err } buildLog.Print("Program loaded and type checked") prog := ssautil.CreateProgram(lprog, ssa.GlobalDebug|ssa.BareInits) // Prepare Config for whole-program pointer analysis. ptaConf, err := setupPTA(prog, lprog, conf.PtaLog) ignoredPkgs := []string{} if len(conf.BadPkgs) == 0 { prog.Build() } else { for _, info := range lprog.AllPackages { if reason, badPkg := conf.BadPkgs[info.Pkg.Name()]; badPkg { buildLog.Printf("Skip package: %s (%s)", info.Pkg.Name(), reason) ignoredPkgs = append(ignoredPkgs, info.Pkg.Name()) } else { prog.Package(info.Pkg).Build() } } } return &SSAInfo{ BuildConf: conf, IgnoredPkgs: ignoredPkgs, FSet: lprog.Fset, Prog: prog, PtaConf: ptaConf, Logger: buildLog, }, nil } // CallGraph builds the call graph from the 'main.main' function. // // The call graph is rooted at 'main.main', all nodes appear only once in the // graph. A side-effect of building the call graph is obtaining a list of // functions used in a program (as functions not called will not appear in the // CallGraph). // TODO(nickng) cache previously built CallGraph. func (info *SSAInfo) CallGraph() *callgraph.Node { mainPkg := MainPkg(info.Prog) if mainFunc := mainPkg.Func("main"); mainFunc != nil { return callgraph.Build(mainFunc) } return nil // No main pkg --> nothing is called } // DecodePos converts a token.Pos (offset) to an actual token.Position. // // This is just a shortcut to .FSet.Position. func (info *SSAInfo) DecodePos(pos token.Pos) token.Position { return info.FSet.Position(pos) } // NewPta performs a custom pointer analysis on given values. func (info *SSAInfo) NewPta(vals ...ssa.Value) *pointer.Result { for _, val := range vals { info.PtaConf.AddQuery(val) } result, err := pointer.Analyze(info.PtaConf) if err != nil { info.Logger.Print("NewPta:", ErrPtaInternal) } return result } // FindChan performs a ptr analysis on a given chan ssa.Value, returns a list of // related ChanOp on the chan. func (info *SSAInfo) FindChan(ch ssa.Value) []ChanOp { chanOps := purgeChanOps(progChanOps(info.Prog), ch) for _, op := range chanOps { info.PtaConf.AddQuery(op.Value) } result, err := pointer.Analyze(info.PtaConf) if err != nil { info.Logger.Print("FindChan failed:", ErrPtaInternal) } queryCh := result.Queries[ch] var ops []ChanOp for _, label := range queryCh.PointsTo().Labels() { // Add MakeChan to result ops = append(ops, ChanOp{label.Value(), ChanMake, label.Pos()}) } for _, op := range chanOps { if ptr, ok := result.Queries[op.Value]; ok && ptr.MayAlias(queryCh) { ops = append(ops, op) } } return ops } ================================================ FILE: ssabuilder/util.go ================================================ package ssabuilder import ( "golang.org/x/tools/go/ssa" ) // GetMainPkg returns main package of a command. func MainPkg(prog *ssa.Program) *ssa.Package { pkgs := prog.AllPackages() for _, pkg := range pkgs { if pkg.Pkg.Name() == "main" { return pkg } } return nil // Not found } ================================================ FILE: static/script.js ================================================ // goCode returns string of Go code extracted from #go div. function goCode() { var code=''; $.each($('#go pre'), function(i, val) { code += val.innerText + '\n'; }); return code; } function migoCode() { var code=''; $.each($('#out pre'), function(i, val) { code += val.innerText + '\n'; }); return code; } // writeCode puts s into the #out div. function writeTo(s, selector) { $(selector).empty(); var strs = s.split('\n'); for (var i=0; i').html(strs[i]+'\n')); } } // reportTime puts t to the time div. function reportTime(t) { if (t!=undefined && t!=null && t!='') { $('#time').html('Last operation completed in '+t); } else { $('#time').html(''); } } (function(){ $('#ssa').on('click', function() { reportTime(''); $.ajax({ url: '/ssa', type: 'POST', data: goCode(), async: true, success: function(msg) { writeTo(msg, '#out'); $('#out').attr('lang', 'Go SSA') } }); }); $('#cfsm').on('click', function() { reportTime(''); $.ajax({ url: '/cfsm', type: 'POST', data: goCode(), async: true, success: function(msg) { var obj=JSON.parse(msg); if (obj!=null && obj.CFSM!=null) { writeTo(obj.CFSM, '#out'); reportTime(obj.time); $('#out').attr('lang', 'CFSM'); } else { writeTo("JSON error", '#out'); } } }); }); $('#migo').on('click', function() { reportTime(''); $.ajax({ url: '/migo', type: 'POST', data: goCode(), async: true, success: function(msg) { var obj=JSON.parse(msg); if (obj!=null && obj.MiGo!=null) { writeTo(obj.MiGo, '#out'); reportTime(obj.time); $('#out').attr('lang', 'MiGo'); } else { writeTo("JSON error", '#out'); } } }); }); $('#example').on('click', function() { reportTime(''); $.ajax({ url: '/load', type: 'POST', data: $('#examples option:selected').text(), async: true, success: function(msg) { writeTo(msg, '#go'); writeTo('No output.', '#out'); $('#out').removeAttr('lang'); } }); }); $('#gong').on('click', function() { if ($('#out').attr('lang') != 'MiGo') { return false } reportTime(''); $.ajax({ url: '/gong', type: 'POST', data: migoCode(), async: true, success: function(msg) { var obj = JSON.parse(msg); if (obj!=null&&obj.Gong!=null) { writeTo(obj.Gong, '#gong-output'); reportTime(obj.time); $('#gong-wrap').addClass('visible'); } else { writeTo("JSON error", '#gong-output'); } } }); }); $('#gong-output-close').on('click', function() { $('#gong-wrap').removeClass('visible'); }) $('#synthesis').on('click', function() { if ($('#out').attr('lang') != 'CFSM') { return false } reportTime(''); $.ajax({ url: '/synthesis?chan='+$('#chan-cfsm').val(), type: 'POST', data: migoCode(), async: true, success: function(msg) { var obj = JSON.parse(msg); if (obj!=null&&obj.SMC!=null) { writeTo(obj.SMC, '#synthesis-output'); $('#synthesis-global').html(obj.Global) $('#synthesis-machines').html(obj.Machines) reportTime(obj.time); $('#synthesis-wrap').addClass('visible'); } else { writeTo("JSON error", '#synthesis-output'); } } }); }); $('#synthesis-output-close').on('click', function() { $('#synthesis-wrap').removeClass('visible'); }) writeTo('// Write Go code here\n' + 'package main\n\n' + 'import "fmt"\n\n' + 'func main() {\n' + ' ch := make(chan int) // Create channel ch\n' + ' go func(ch chan int) { // Spawn goroutine\n' + ' ch <- 42 // Send value to ch\n' + ' }(ch)\n' + ' fmt.Println(<-ch) // Recv value from ch\n' + '}\n', '#go'); })() ================================================ FILE: static/style.css ================================================ html, body { margin: 0; padding: 0; width: 100%; height: 100%; font-family: 'Roboto', sans-serif; } div.gocode, div.generated { position: relative; } div.gocode > div.buttons, div.generated > div.buttons { position: absolute; bottom: 10px; right: 10px; } div.output { opacity: 0.95; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 10px; background: #222; color: #fff; width: 50%; right: 0; bottom: 0; position: absolute; } div#synthesis-wrap, div#gong-wrap { position: absolute; display: none; right: 10px; bottom: 10px; } div#synthesis-wrap.visible, div#gong-wrap.visible { opacity: 0.95; position: absolute; display: inline-block; right: 10px; bottom: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; padding: 10px; background: #222; color: #fff; } div.code { white-space: pre; background: #eee; font-family: 'Fira Mono', monospace; font-weight: 400; padding: 20px; -webkit-font-smoothing: antialiased; -webkit-line-break: after-white-space; -webkit-user-modify: read-write; } div#go:before { content: 'Go'; } div#out:before { content: attr(lang); } div#go:before, div#out[lang]:before { position: absolute; top: 0; right: 0; background: #888; padding: 10px; color: #fff; opacity: 0.8; font-family: 'Fira Mono', monospace; -webkit-border-bottom-left-radius: 5; -moz-border-bottom-left-radius: 5; border-bottom-left-radius: 5px; } div#out:not([lang]) + div.buttons, div#out[lang] + div.buttons { display: none; } div#out[lang='MiGo'] + div.buttons, div#out[lang='CFSM'] + div.buttons { display: block; } #time { font-family: 'Roboto'; font-weight: 100; font-size: smaller; } div.code pre { font-family: 'Fira Mono', monospace; line-height: 24px; margin: 0px 0px 0px 0px; word-wrap: break-word; overflow-x: visible; overflow-y: visible; -webkit-font-smoothing: antialiased; -webkit-line-break: after-white-space; -webkit-user-modify: read-write; } div.code pre span { white-space: pre; } div.output div.buttons { position: absolute; bottom: 10px; right: 10px; color: #000; } div.controls { width: 100%; background: #ccc;/* url(/static/logo.png) center right no-repeat;*/ padding: 10px 0; } div.buttons button, div.controls button, div.controls select { background: #44b6d0; -webkit-border-radius: 10; -moz-border-radius: 10; border-radius: 10px; color: #ffffff; padding: 5px 10px 5px 10px; text-decoration: none; border-style: solid; border-width: 2px; border-color: #298ba3; margin: 0 10px; } /* Half-buttons */ div.output div.buttons button.close, div.output div.buttons button.kill, div.controls .right { -webkit-border-bottom-left-radius: 0; -moz-border-bottom-left-radius: 0; border-bottom-left-radius: 0; -webkit-border-top-left-radius: 0; -moz-border-top-left-radius: 0; border-top-left-radius: 0; margin-left: 0; } div.output div.buttons button.run, div.output div.buttons button.kill, div.controls .left { -webkit-border-bottom-right-radius: 0; -moz-border-bottom-right-radius: 0; border-bottom-right-radius: 0; -webkit-border-top-right-radius: 0; -moz-border-top-right-radius: 0; border-top-right-radius: 0; margin-right: 0; } div.controls button:hover { background: #28849b; text-decoration: none; } div#synthesis-graphics { max-width: 1000px; overflow-x: auto; background: #ccc; } div#synthesis-graphics div#synthesis-machines, div#synthesis-graphics div#synthesis-global { display: table-cell; padding: 5px; vertical-align: middle; } ================================================ FILE: talks/basic/concurrency.go ================================================ // +build OMIT package main import ( "fmt" "time" ) func deepThought(replyCh chan int) { time.Sleep(75 * time.Millisecond) replyCh <- 42 // HLsendrecv } func main() { ch := make(chan int) // HLmakechan go deepThought(ch) // HLdl answer := <-ch // HLsendrecv fmt.Printf("The answer is %d\n", answer) } // END OMIT ================================================ FILE: talks/basic/fanin.go ================================================ // +build OMIT package main import ( "fmt" ) func work(out chan<- int) { for { out <- 42 } } func fanin(input1, input2 <-chan int) <-chan int { c := make(chan int) go func() { for { select { case s := <-input1: c <- s case s := <-input2: c <- s } } }() return c } func main() { input1 := make(chan int) input2 := make(chan int) go work(input1) go work(input2) c := fanin(input1, input2) for { fmt.Println(<-c) } } ================================================ FILE: talks/basic/select.go ================================================ // +build OMIT package main import ( "fmt" "math/rand" "time" ) func calc(ch chan int) { rand.Seed(time.Now().Unix()) val := rand.Intn(5) time.Sleep(time.Duration(val) * time.Microsecond) ch <- val } func main() { ch1, ch2 := make(chan int), make(chan int) go calc(ch1) go calc(ch2) select { case ans := <-ch1: fmt.Println("Answer from ch1: ", ans) case ans := <-ch2: fmt.Println("Answer from ch2: ", ans) } } ================================================ FILE: talks/conf-cc-2016/deadlock-global.html ================================================
CFSMs are numbered as below
  • 0. channel ch
  • 1. channel done
  • 2. main → done 1 ? int → done 1 ? int
  • 3. Send :19 → ch 0 ! int
  • 4. Recv :20 → ch 0 ? int → done 1 ! done
  • 5. Recv :21 → ch 0 ? int → done 1 ! done
================================================ FILE: talks/conf-cc-2016/fanin-global.html ================================================
// +build OMIT

package main

import (
	"fmt"
)

func work(out chan<- int) {
	for {
		out <- 42
	}
}
func fanin(input1, input2 <-chan int) <-chan int {
    c := make(chan int)
    go func() {
        for {
            select {
            case s := <-input1:
                c <- s
            case s := <-input2:
                c <- s
            }
        }
    }()
    return c
}

func main() {
	input1 := make(chan int)
	input2 := make(chan int)
	go work(input1)
	go work(input2)
	c := fanin(input1, input2)
	for {
		fmt.Println(<-c)
	}
}
================================================ FILE: talks/conf-cc-2016/select.html ================================================
// +build OMIT

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func calc(ch chan int) {
	rand.Seed(time.Now().Unix())
	val := rand.Intn(5)
	time.Sleep(time.Duration(val) * time.Microsecond)
	ch <- val
}

func main() {
    ch1, ch2 := make(chan int), make(chan int)
    go calc(ch1)
    go calc(ch2)
    select {
    case ans := <-ch1:
        fmt.Println("Answer from ch1: ", ans)
    case ans := <-ch2:
        fmt.Println("Answer from ch2: ", ans)
    }
}
================================================ FILE: talks/conf-cc-2016/ssa.html ================================================
func main():
0:                                                    entry P:0 S:0
    t0 = make chan int 0:int                           chan int
    t1 = make chan int 0:int                           chan int
    t2 = changetype chan<- int <- chan int (t0)      chan<- int
    go Send(t2)
    t3 = changetype <-chan int <- chan int (t0)      <-chan int
    t4 = changetype chan<- int <- chan int (t1)      chan<- int
    go RecvAck(t3, t4)
    t5 = changetype <-chan int <- chan int (t0)      <-chan int
    t6 = changetype chan<- int <- chan int (t1)      chan<- int
    go RecvAck(t5, t6)
    go main$1()
    t7 = <-t1                                               int
    t8 = <-t1                                               int
    return
================================================ FILE: talks/conf-cc-2016/type-inference.html ================================================
func main() {
    ch, done := make(chan int), make(chan int)
    go Send(ch)
    go RecvAck(ch, done)
    go RecvAck(ch, done) // OOPS

    // Anonymous goroutine: Some long running work (e.g. http service)
    go func() {
        for i := 0; i < 3; i++ {
            fmt.Println("Working #", i); time.Sleep(1 * time.Second)
        }
    }()
    result = <-done
    result = <-done // OOPS
    fmt.Println("Result is ", result)
}
================================================ FILE: talks/conf-cc-2016.slide ================================================ Static Deadlock Detection for Go by Global Session Graph Synthesis *Nicholas*Ng* & Nobuko Yoshida Department of Computing Imperial College London {nickng,n.yoshida}@imperial.ac.uk http://mrg.doc.ic.ac.uk * Contributions - Static deadlock detection tool _dingo-hunter_ - Deadlock detection based on session types - *Infer* session types as Communicating Automata - *Synthesise* global session graphs from CA .image conf-cc-2016/overview.png * Go and Concurrency - Developed by Google for multi-core programming - Concurrency model built on CSP (process calculi) - Message-passing *communication* over channels " _Do_not_communicate_by_sharing_memory_; _instead_, _share_memory_by_communicating_. " -- Effective Go (developer guide) * Go concurrency: Goroutines and Channels - Goroutines: lightweight threads - Channels: bounded queues (default size 0) - Go concurrency = compose goroutines & coordinate with channels .play basic/concurrency.go /^func deepThought/,/END OMIT$/ HLsendrecv * Concurrency Problems - Deadlocks [[#colour:red][☹]] - _Some_ goroutines are blocked waiting forever .play basic/concurrency.go /^func deepThought/,/END OMIT$/ HLdl * fatal error: all goroutines are asleep - deadlock! - Go has a _runtime_ [[http://github.com/golang/go/blob/d2c81ad84776edfb4c790666f1d80554b4393d46/src/runtime/proc.go#L3243][deadlock detector]] - No. of running goroutines: if `run`>`0` ⟶ OK [[#colour:green][✓]]; if `run`==`0` ⟶ deadlock [[#colour:red][☹]] .code snippet/proc.txt * Runtime deadlock detection - No false positive - Only if deadlock during execution - Only *global* deadlocks: if ALL goroutines are blocked * Defeating runtime deadlock detection .play deadlock/deadlock.go /^func Send/,/^}/ HLoops This will be our running example * Static Analysis & Deadlock Detection by Global Graph Synthesis * Static Analysis & Deadlock Detection by Global Graph Synthesis .image conf-cc-2016/overview.png - Explore all branches (vs. only execution path for runtime checker) - Approach based on *Multiparty*Session*Types* (MPST) [1] - Guarantee communication-safety & *deadlock-freedom* for n-party interactions .caption [1]: Honda, Yoshida, Carbone, _Multiparty_Asynchronous_Session_Types_ , POPL'08, J. ACM * The Multiparty Session Types (MPST) framework .image conf-cc-2016/mpst.png - *Global*Graph*Synthesis* [2] [[#colour:blue][local type(s)]] ⟶ [[#colour:red][global type]] - [[#colour:blue][Local types]] as _Communicating_Automata_ [3] - [[#colour:red][Global types]] as _graphical_choreographies_ - Safety guaranteed by *multiparty*compatibility* property (global) .caption [2]: Lange, Tuosto, Yoshida, _From_Communicating_Machines_to_Graphical_Choreographies_ , POPL'15 .caption [3]: Brand, Zafiropulo, _On_communicating_finite-state_machines_ , J. ACM Vol. 30 No. 2, 1983 * Type inference from Go code - Control flow graph as Finite State Machine (FSM), per goroutine - + Communication primitives: `make(chan`T)`, send, receive .html conf-cc-2016/type-inference.html * Inferred local session types - For simplicity: real variable names - Note: `main$1:24` (work anonymous function) has no communication .image conf-cc-2016/local-type.png .caption Variable names: `ch`=`main.t0`, `done`=`main.t1` * Inferred Local Types to Goroutine Automata - Local session types ⟹ _Communicating_Automata_ - *Receive* is ? transition, *Send* is ! transition .image conf-cc-2016/cfsm.png #.caption [4]: Deniélou, Yoshida, _Multiparty_Compatibility_in_Communicating_Automata:_Characterisation_and_Synthesis_ , ICALP 2013 * Channel Automata - Channels in *Communicating*Automata* - _fixed_ point-to-point links - *Go*channels* - _shared_, do not represent _fixed_ endpoint - 2 goroutines writing to same channel valid [[#colour:green][✓]] .image conf-cc-2016/channel-problem.png .caption Note: Go channels valid regardless of the deadlock * Channel Automata - *Channel*Automata* defers selection to a machine (for each channel) - *Unbuffered*channel* q0 → receive → send → q0 .image conf-cc-2016/channel-cfsm.png .caption Channel Automata _done_ & _ch_ (only essential transitions) * Bonus: Select non-deterministic choice - Switch-case for communication .html conf-cc-2016/select.html * Global Graph Synthesis - Join both automata to get overall *global* graph - All synchronous transitions, [[#colour:blue][A: `ch!int`]] + [[#colour:blue][B: `ch?int`]] becomes [[#colour:red][A → B: int]] .image conf-cc-2016/global.png * Global Graph Synthesis: What is safe? - *Multiparty*Compatibility* property on global graph - *Representability* All [Goroutine] automata are "represented" in global graph - *Branching*condition* Branches are propagated to all machines in choice .image conf-cc-2016/compatibility.png * Fan-in pattern - Merging pattern with `select` .html conf-cc-2016/fanin-global.html * Bigger example: htcat - Concurrent HTTP GETs .image conf-cc-2016/htcat-snippet.png .caption [[https://github.com/htcat/htcat][github.com:htcat/htcat]] (721 LoC) * Bigger example: htcat - Concurrent HTTP GETs #.background conf-cc-2016/htcat.svg - 7xx lines - 9632 nodes - 11 Goroutine Automata / 8 Channel Automata - Safe [[#colour:green][✓]] - Error handling code: conditional new goroutine if err != nil { go cat.d.cancel(err) return } * Conclusion - Static analysis tool for Go - Detect deadlock through global session graph synthesis - Applied to open source code base Static analysis tool - [[https://github.com/nickng/dingo-hunter][github.com/nickng/dingo-hunter]] Synthesis tool (POPL'15 artifact) - [[https://bitbucket.org/julien-lange/gmc-synthesis][bitbucket.org/julien-lange/gmc-synthesis]] .image conf-cc-2016/hunter.png * Future Work - *Buffered*channels* asynchronous communication over channels - *Dynamic*patterns* Expand bounded ranges (e.g. `for`i:=0;`i<10;`i++`{...}`) .image conf-cc-2016/overview.png ================================================ FILE: talks/deadlock/deadlock.go ================================================ // +build OMIT package main import ( "fmt" "time" ) var ( result int ) func Send(ch chan<- int) { ch <- 42 } // Send func RecvAck(ch <-chan int, done chan<- int) { v := <-ch; done <- v } // Recv then Send func main() { ch, done := make(chan int), make(chan int) go Send(ch) go RecvAck(ch, done) go RecvAck(ch, done) // OOPS // HLoops // Anonymous goroutine: Some long running work (e.g. http service) go func() { for i := 0; i < 3; i++ { fmt.Println("Working #", i) time.Sleep(1 * time.Second) } }() result = <-done result = <-done // OOPS // HLoops fmt.Println("Result is ", result) } ================================================ FILE: talks/snippet/proc.txt ================================================ // From Go source code - https://github.com/golang/go // File runtime/proc.go // +build OMIT func checkdead() { ... // -1 for sysmon run := sched.mcount - sched.nmidle - sched.nmidlelocked - 1 // HL if run > 0 { return } ... getg().m.throwing = -1 // do not dump full stacks throw("all goroutines are asleep - deadlock!") } ================================================ FILE: talks/static/article.css ================================================ body { margin: 0; font-family: Helvetica, Arial, sans-serif; font-size: 16px; } pre, code { font-family: Menlo, monospace; font-size: 14px; } pre { line-height: 18px; margin: 0; padding: 0; } a { color: #375EAB; text-decoration: none; } a:hover { text-decoration: underline; } p, ul, ol { margin: 20px; } h1, h2, h3, h4 { margin: 20px 0; padding: 0; color: #375EAB; font-weight: bold; } h1 { font-size: 24px; } h2 { font-size: 20px; background: #E0EBF5; padding: 2px 5px; } h3 { font-size: 20px; } h3, h4 { margin: 20px 5px; } h4 { font-size: 16px; } div#heading { float: left; margin: 0 0 10px 0; padding: 21px 0; font-size: 20px; font-weight: normal; } div#topbar { background: #E0EBF5; height: 64px; overflow: hidden; } body { text-align: center; } div#page { width: 100%; } div#page > .container, div#topbar > .container { text-align: left; margin-left: auto; margin-right: auto; padding: 0 20px; width: 900px; } div#page.wide > .container, div#topbar.wide > .container { width: auto; } div#footer { text-align: center; color: #666; font-size: 14px; margin: 40px 0; } .author p { margin: 20, 0, 0, 0px; } div.code, div.output { margin: 20px; padding: 10px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } div.code { background: #e9e9e9; } div.output { background: black; } div.output .stdout { color: #e6e6e6; } div.output .stderr { color: rgb(244, 74, 63); } div.output .system { color: rgb(255, 209, 77) } .buttons { margin-left: 20px; } div.output .buttons { margin-left: 0; margin-bottom: 10px; } #toc { float: right; margin: 0px 10px; padding: 10px; border: 1px solid #e5ecf9; background-color: white; max-width: 33%; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } #toc ul, #toc a { list-style-type: none; padding-left: 10px; color: black; margin: 0px; } ================================================ FILE: talks/static/dir.css ================================================ /* copied from $GOROOT/doc/style.css */ body { margin: 0; font-family: Helvetica, Arial, sans-serif; font-size: 16px; } pre, code { font-family: Menlo, monospace; font-size: 14px; } pre { line-height: 18px; } pre .comment { color: #375EAB; } pre .highlight, pre .highlight-comment, pre .selection-highlight, pre .selection-highlight-comment { background: #FFFF00; } pre .selection, pre .selection-comment { background: #FF9632; } pre .ln { color: #999; } body { color: #222; } a, .exampleHeading .text { color: #375EAB; text-decoration: none; } a:hover, .exampleHeading .text:hover { text-decoration: underline; } p, pre, ul, ol { margin: 20px; } pre { background: #e9e9e9; padding: 10px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } h1, h2, h3, h4, .rootHeading { margin: 20px 0; padding: 0; color: #375EAB; font-weight: bold; } h1 { font-size: 24px; } h2 { font-size: 20px; background: #E0EBF5; padding: 2px 5px; } h3 { font-size: 20px; } h3, h4 { margin: 20px 5px; } h4 { font-size: 16px; } dl { margin: 20px; } dd { margin: 2px 20px; } dl, dd { font-size: 14px; } div#nav table td { vertical-align: top; } div#heading { float: left; margin: 0 0 10px 0; padding: 21px 0; font-size: 20px; font-weight: normal; } div#heading a { color: #222; text-decoration: none; } div#topbar { background: #E0EBF5; height: 64px; } body { text-align: center; } div#page, div#topbar > .container { clear: both; text-align: left; margin-left: auto; margin-right: auto; padding: 0 20px; width: 900px; } div#page.wide, div#topbar > .wide { width: auto; } div#plusone { float: right; } div#footer { color: #666; font-size: 14px; margin: 40px 0; } div#menu > a, div#menu > input { padding: 10px; text-decoration: none; font-size: 16px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; } div#menu > a, div#menu > input { border: 1px solid #375EAB; } div#menu > a { color: white; background: #375EAB; } div#menu { float: right; min-width: 590px; padding: 10px 0; text-align: right; } div#menu > a { margin-right: 5px; margin-bottom: 10px; padding: 10px; } div#menu > input { position: relative; top: 1px; width: 60px; background: white; color: #222; } div#menu > input.inactive { color: #999; } ================================================ FILE: talks/static/dir.js ================================================ // Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // copied from $GOROOT/doc/godocs.js function bindEvent(el, e, fn) { if (el.addEventListener){ el.addEventListener(e, fn, false); } else if (el.attachEvent){ el.attachEvent('on'+e, fn); } } function godocs_bindSearchEvents() { var search = document.getElementById('search'); if (!search) { // no search box (index disabled) return; } function clearInactive() { if (search.className == "inactive") { search.value = ""; search.className = ""; } } function restoreInactive() { if (search.value !== "") { return; } if (search.type != "search") { search.value = search.getAttribute("placeholder"); } search.className = "inactive"; } restoreInactive(); bindEvent(search, 'focus', clearInactive); bindEvent(search, 'blur', restoreInactive); } bindEvent(window, 'load', godocs_bindSearchEvents); ================================================ FILE: talks/static/jquery-ui.js ================================================ /*! jQuery UI - v1.10.2 - 2013-03-20 * http://jqueryui.com * Includes: jquery.ui.core.js, jquery.ui.widget.js, jquery.ui.mouse.js, jquery.ui.resizable.js * Copyright 2013 jQuery Foundation and other contributors Licensed MIT */ (function(e,t){function i(t,i){var a,n,r,o=t.nodeName.toLowerCase();return"area"===o?(a=t.parentNode,n=a.name,t.href&&n&&"map"===a.nodeName.toLowerCase()?(r=e("img[usemap=#"+n+"]")[0],!!r&&s(r)):!1):(/input|select|textarea|button|object/.test(o)?!t.disabled:"a"===o?t.href||i:i)&&s(t)}function s(t){return e.expr.filters.visible(t)&&!e(t).parents().addBack().filter(function(){return"hidden"===e.css(this,"visibility")}).length}var a=0,n=/^ui-id-\d+$/;e.ui=e.ui||{},e.extend(e.ui,{version:"1.10.2",keyCode:{BACKSPACE:8,COMMA:188,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,LEFT:37,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SPACE:32,TAB:9,UP:38}}),e.fn.extend({focus:function(t){return function(i,s){return"number"==typeof i?this.each(function(){var t=this;setTimeout(function(){e(t).focus(),s&&s.call(t)},i)}):t.apply(this,arguments)}}(e.fn.focus),scrollParent:function(){var t;return t=e.ui.ie&&/(static|relative)/.test(this.css("position"))||/absolute/.test(this.css("position"))?this.parents().filter(function(){return/(relative|absolute|fixed)/.test(e.css(this,"position"))&&/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0):this.parents().filter(function(){return/(auto|scroll)/.test(e.css(this,"overflow")+e.css(this,"overflow-y")+e.css(this,"overflow-x"))}).eq(0),/fixed/.test(this.css("position"))||!t.length?e(document):t},zIndex:function(i){if(i!==t)return this.css("zIndex",i);if(this.length)for(var s,a,n=e(this[0]);n.length&&n[0]!==document;){if(s=n.css("position"),("absolute"===s||"relative"===s||"fixed"===s)&&(a=parseInt(n.css("zIndex"),10),!isNaN(a)&&0!==a))return a;n=n.parent()}return 0},uniqueId:function(){return this.each(function(){this.id||(this.id="ui-id-"+ ++a)})},removeUniqueId:function(){return this.each(function(){n.test(this.id)&&e(this).removeAttr("id")})}}),e.extend(e.expr[":"],{data:e.expr.createPseudo?e.expr.createPseudo(function(t){return function(i){return!!e.data(i,t)}}):function(t,i,s){return!!e.data(t,s[3])},focusable:function(t){return i(t,!isNaN(e.attr(t,"tabindex")))},tabbable:function(t){var s=e.attr(t,"tabindex"),a=isNaN(s);return(a||s>=0)&&i(t,!a)}}),e("").outerWidth(1).jquery||e.each(["Width","Height"],function(i,s){function a(t,i,s,a){return e.each(n,function(){i-=parseFloat(e.css(t,"padding"+this))||0,s&&(i-=parseFloat(e.css(t,"border"+this+"Width"))||0),a&&(i-=parseFloat(e.css(t,"margin"+this))||0)}),i}var n="Width"===s?["Left","Right"]:["Top","Bottom"],r=s.toLowerCase(),o={innerWidth:e.fn.innerWidth,innerHeight:e.fn.innerHeight,outerWidth:e.fn.outerWidth,outerHeight:e.fn.outerHeight};e.fn["inner"+s]=function(i){return i===t?o["inner"+s].call(this):this.each(function(){e(this).css(r,a(this,i)+"px")})},e.fn["outer"+s]=function(t,i){return"number"!=typeof t?o["outer"+s].call(this,t):this.each(function(){e(this).css(r,a(this,t,!0,i)+"px")})}}),e.fn.addBack||(e.fn.addBack=function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}),e("").data("a-b","a").removeData("a-b").data("a-b")&&(e.fn.removeData=function(t){return function(i){return arguments.length?t.call(this,e.camelCase(i)):t.call(this)}}(e.fn.removeData)),e.ui.ie=!!/msie [\w.]+/.exec(navigator.userAgent.toLowerCase()),e.support.selectstart="onselectstart"in document.createElement("div"),e.fn.extend({disableSelection:function(){return this.bind((e.support.selectstart?"selectstart":"mousedown")+".ui-disableSelection",function(e){e.preventDefault()})},enableSelection:function(){return this.unbind(".ui-disableSelection")}}),e.extend(e.ui,{plugin:{add:function(t,i,s){var a,n=e.ui[t].prototype;for(a in s)n.plugins[a]=n.plugins[a]||[],n.plugins[a].push([i,s[a]])},call:function(e,t,i){var s,a=e.plugins[t];if(a&&e.element[0].parentNode&&11!==e.element[0].parentNode.nodeType)for(s=0;a.length>s;s++)e.options[a[s][0]]&&a[s][1].apply(e.element,i)}},hasScroll:function(t,i){if("hidden"===e(t).css("overflow"))return!1;var s=i&&"left"===i?"scrollLeft":"scrollTop",a=!1;return t[s]>0?!0:(t[s]=1,a=t[s]>0,t[s]=0,a)}})})(jQuery);(function(e,t){var i=0,s=Array.prototype.slice,n=e.cleanData;e.cleanData=function(t){for(var i,s=0;null!=(i=t[s]);s++)try{e(i).triggerHandler("remove")}catch(a){}n(t)},e.widget=function(i,s,n){var a,r,o,h,l={},u=i.split(".")[0];i=i.split(".")[1],a=u+"-"+i,n||(n=s,s=e.Widget),e.expr[":"][a.toLowerCase()]=function(t){return!!e.data(t,a)},e[u]=e[u]||{},r=e[u][i],o=e[u][i]=function(e,i){return this._createWidget?(arguments.length&&this._createWidget(e,i),t):new o(e,i)},e.extend(o,r,{version:n.version,_proto:e.extend({},n),_childConstructors:[]}),h=new s,h.options=e.widget.extend({},h.options),e.each(n,function(i,n){return e.isFunction(n)?(l[i]=function(){var e=function(){return s.prototype[i].apply(this,arguments)},t=function(e){return s.prototype[i].apply(this,e)};return function(){var i,s=this._super,a=this._superApply;return this._super=e,this._superApply=t,i=n.apply(this,arguments),this._super=s,this._superApply=a,i}}(),t):(l[i]=n,t)}),o.prototype=e.widget.extend(h,{widgetEventPrefix:r?h.widgetEventPrefix:i},l,{constructor:o,namespace:u,widgetName:i,widgetFullName:a}),r?(e.each(r._childConstructors,function(t,i){var s=i.prototype;e.widget(s.namespace+"."+s.widgetName,o,i._proto)}),delete r._childConstructors):s._childConstructors.push(o),e.widget.bridge(i,o)},e.widget.extend=function(i){for(var n,a,r=s.call(arguments,1),o=0,h=r.length;h>o;o++)for(n in r[o])a=r[o][n],r[o].hasOwnProperty(n)&&a!==t&&(i[n]=e.isPlainObject(a)?e.isPlainObject(i[n])?e.widget.extend({},i[n],a):e.widget.extend({},a):a);return i},e.widget.bridge=function(i,n){var a=n.prototype.widgetFullName||i;e.fn[i]=function(r){var o="string"==typeof r,h=s.call(arguments,1),l=this;return r=!o&&h.length?e.widget.extend.apply(null,[r].concat(h)):r,o?this.each(function(){var s,n=e.data(this,a);return n?e.isFunction(n[r])&&"_"!==r.charAt(0)?(s=n[r].apply(n,h),s!==n&&s!==t?(l=s&&s.jquery?l.pushStack(s.get()):s,!1):t):e.error("no such method '"+r+"' for "+i+" widget instance"):e.error("cannot call methods on "+i+" prior to initialization; "+"attempted to call method '"+r+"'")}):this.each(function(){var t=e.data(this,a);t?t.option(r||{})._init():e.data(this,a,new n(r,this))}),l}},e.Widget=function(){},e.Widget._childConstructors=[],e.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"
",options:{disabled:!1,create:null},_createWidget:function(t,s){s=e(s||this.defaultElement||this)[0],this.element=e(s),this.uuid=i++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=e.widget.extend({},this.options,this._getCreateOptions(),t),this.bindings=e(),this.hoverable=e(),this.focusable=e(),s!==this&&(e.data(s,this.widgetFullName,this),this._on(!0,this.element,{remove:function(e){e.target===s&&this.destroy()}}),this.document=e(s.style?s.ownerDocument:s.document||s),this.window=e(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:e.noop,_getCreateEventData:e.noop,_create:e.noop,_init:e.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(e.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:e.noop,widget:function(){return this.element},option:function(i,s){var n,a,r,o=i;if(0===arguments.length)return e.widget.extend({},this.options);if("string"==typeof i)if(o={},n=i.split("."),i=n.shift(),n.length){for(a=o[i]=e.widget.extend({},this.options[i]),r=0;n.length-1>r;r++)a[n[r]]=a[n[r]]||{},a=a[n[r]];if(i=n.pop(),s===t)return a[i]===t?null:a[i];a[i]=s}else{if(s===t)return this.options[i]===t?null:this.options[i];o[i]=s}return this._setOptions(o),this},_setOptions:function(e){var t;for(t in e)this._setOption(t,e[t]);return this},_setOption:function(e,t){return this.options[e]=t,"disabled"===e&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!t).attr("aria-disabled",t),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(i,s,n){var a,r=this;"boolean"!=typeof i&&(n=s,s=i,i=!1),n?(s=a=e(s),this.bindings=this.bindings.add(s)):(n=s,s=this.element,a=this.widget()),e.each(n,function(n,o){function h(){return i||r.options.disabled!==!0&&!e(this).hasClass("ui-state-disabled")?("string"==typeof o?r[o]:o).apply(r,arguments):t}"string"!=typeof o&&(h.guid=o.guid=o.guid||h.guid||e.guid++);var l=n.match(/^(\w+)\s*(.*)$/),u=l[1]+r.eventNamespace,c=l[2];c?a.delegate(c,u,h):s.bind(u,h)})},_off:function(e,t){t=(t||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,e.unbind(t).undelegate(t)},_delay:function(e,t){function i(){return("string"==typeof e?s[e]:e).apply(s,arguments)}var s=this;return setTimeout(i,t||0)},_hoverable:function(t){this.hoverable=this.hoverable.add(t),this._on(t,{mouseenter:function(t){e(t.currentTarget).addClass("ui-state-hover")},mouseleave:function(t){e(t.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(t){this.focusable=this.focusable.add(t),this._on(t,{focusin:function(t){e(t.currentTarget).addClass("ui-state-focus")},focusout:function(t){e(t.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(t,i,s){var n,a,r=this.options[t];if(s=s||{},i=e.Event(i),i.type=(t===this.widgetEventPrefix?t:this.widgetEventPrefix+t).toLowerCase(),i.target=this.element[0],a=i.originalEvent)for(n in a)n in i||(i[n]=a[n]);return this.element.trigger(i,s),!(e.isFunction(r)&&r.apply(this.element[0],[i].concat(s))===!1||i.isDefaultPrevented())}},e.each({show:"fadeIn",hide:"fadeOut"},function(t,i){e.Widget.prototype["_"+t]=function(s,n,a){"string"==typeof n&&(n={effect:n});var r,o=n?n===!0||"number"==typeof n?i:n.effect||i:t;n=n||{},"number"==typeof n&&(n={duration:n}),r=!e.isEmptyObject(n),n.complete=a,n.delay&&s.delay(n.delay),r&&e.effects&&e.effects.effect[o]?s[t](n):o!==t&&s[o]?s[o](n.duration,n.easing,a):s.queue(function(i){e(this)[t](),a&&a.call(s[0]),i()})}})})(jQuery);(function(e){var t=!1;e(document).mouseup(function(){t=!1}),e.widget("ui.mouse",{version:"1.10.2",options:{cancel:"input,textarea,button,select,option",distance:1,delay:0},_mouseInit:function(){var t=this;this.element.bind("mousedown."+this.widgetName,function(e){return t._mouseDown(e)}).bind("click."+this.widgetName,function(i){return!0===e.data(i.target,t.widgetName+".preventClickEvent")?(e.removeData(i.target,t.widgetName+".preventClickEvent"),i.stopImmediatePropagation(),!1):undefined}),this.started=!1},_mouseDestroy:function(){this.element.unbind("."+this.widgetName),this._mouseMoveDelegate&&e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate)},_mouseDown:function(i){if(!t){this._mouseStarted&&this._mouseUp(i),this._mouseDownEvent=i;var s=this,n=1===i.which,a="string"==typeof this.options.cancel&&i.target.nodeName?e(i.target).closest(this.options.cancel).length:!1;return n&&!a&&this._mouseCapture(i)?(this.mouseDelayMet=!this.options.delay,this.mouseDelayMet||(this._mouseDelayTimer=setTimeout(function(){s.mouseDelayMet=!0},this.options.delay)),this._mouseDistanceMet(i)&&this._mouseDelayMet(i)&&(this._mouseStarted=this._mouseStart(i)!==!1,!this._mouseStarted)?(i.preventDefault(),!0):(!0===e.data(i.target,this.widgetName+".preventClickEvent")&&e.removeData(i.target,this.widgetName+".preventClickEvent"),this._mouseMoveDelegate=function(e){return s._mouseMove(e)},this._mouseUpDelegate=function(e){return s._mouseUp(e)},e(document).bind("mousemove."+this.widgetName,this._mouseMoveDelegate).bind("mouseup."+this.widgetName,this._mouseUpDelegate),i.preventDefault(),t=!0,!0)):!0}},_mouseMove:function(t){return e.ui.ie&&(!document.documentMode||9>document.documentMode)&&!t.button?this._mouseUp(t):this._mouseStarted?(this._mouseDrag(t),t.preventDefault()):(this._mouseDistanceMet(t)&&this._mouseDelayMet(t)&&(this._mouseStarted=this._mouseStart(this._mouseDownEvent,t)!==!1,this._mouseStarted?this._mouseDrag(t):this._mouseUp(t)),!this._mouseStarted)},_mouseUp:function(t){return e(document).unbind("mousemove."+this.widgetName,this._mouseMoveDelegate).unbind("mouseup."+this.widgetName,this._mouseUpDelegate),this._mouseStarted&&(this._mouseStarted=!1,t.target===this._mouseDownEvent.target&&e.data(t.target,this.widgetName+".preventClickEvent",!0),this._mouseStop(t)),!1},_mouseDistanceMet:function(e){return Math.max(Math.abs(this._mouseDownEvent.pageX-e.pageX),Math.abs(this._mouseDownEvent.pageY-e.pageY))>=this.options.distance},_mouseDelayMet:function(){return this.mouseDelayMet},_mouseStart:function(){},_mouseDrag:function(){},_mouseStop:function(){},_mouseCapture:function(){return!0}})})(jQuery);(function(e){function t(e){return parseInt(e,10)||0}function i(e){return!isNaN(parseInt(e,10))}e.widget("ui.resizable",e.ui.mouse,{version:"1.10.2",widgetEventPrefix:"resize",options:{alsoResize:!1,animate:!1,animateDuration:"slow",animateEasing:"swing",aspectRatio:!1,autoHide:!1,containment:!1,ghost:!1,grid:!1,handles:"e,s,se",helper:!1,maxHeight:null,maxWidth:null,minHeight:10,minWidth:10,zIndex:90,resize:null,start:null,stop:null},_create:function(){var t,i,s,n,a,o=this,r=this.options;if(this.element.addClass("ui-resizable"),e.extend(this,{_aspectRatio:!!r.aspectRatio,aspectRatio:r.aspectRatio,originalElement:this.element,_proportionallyResizeElements:[],_helper:r.helper||r.ghost||r.animate?r.helper||"ui-resizable-helper":null}),this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)&&(this.element.wrap(e("
").css({position:this.element.css("position"),width:this.element.outerWidth(),height:this.element.outerHeight(),top:this.element.css("top"),left:this.element.css("left")})),this.element=this.element.parent().data("ui-resizable",this.element.data("ui-resizable")),this.elementIsWrapper=!0,this.element.css({marginLeft:this.originalElement.css("marginLeft"),marginTop:this.originalElement.css("marginTop"),marginRight:this.originalElement.css("marginRight"),marginBottom:this.originalElement.css("marginBottom")}),this.originalElement.css({marginLeft:0,marginTop:0,marginRight:0,marginBottom:0}),this.originalResizeStyle=this.originalElement.css("resize"),this.originalElement.css("resize","none"),this._proportionallyResizeElements.push(this.originalElement.css({position:"static",zoom:1,display:"block"})),this.originalElement.css({margin:this.originalElement.css("margin")}),this._proportionallyResize()),this.handles=r.handles||(e(".ui-resizable-handle",this.element).length?{n:".ui-resizable-n",e:".ui-resizable-e",s:".ui-resizable-s",w:".ui-resizable-w",se:".ui-resizable-se",sw:".ui-resizable-sw",ne:".ui-resizable-ne",nw:".ui-resizable-nw"}:"e,s,se"),this.handles.constructor===String)for("all"===this.handles&&(this.handles="n,e,s,w,se,sw,ne,nw"),t=this.handles.split(","),this.handles={},i=0;t.length>i;i++)s=e.trim(t[i]),a="ui-resizable-"+s,n=e("
"),n.css({zIndex:r.zIndex}),"se"===s&&n.addClass("ui-icon ui-icon-gripsmall-diagonal-se"),this.handles[s]=".ui-resizable-"+s,this.element.append(n);this._renderAxis=function(t){var i,s,n,a;t=t||this.element;for(i in this.handles)this.handles[i].constructor===String&&(this.handles[i]=e(this.handles[i],this.element).show()),this.elementIsWrapper&&this.originalElement[0].nodeName.match(/textarea|input|select|button/i)&&(s=e(this.handles[i],this.element),a=/sw|ne|nw|se|n|s/.test(i)?s.outerHeight():s.outerWidth(),n=["padding",/ne|nw|n/.test(i)?"Top":/se|sw|s/.test(i)?"Bottom":/^e$/.test(i)?"Right":"Left"].join(""),t.css(n,a),this._proportionallyResize()),e(this.handles[i]).length},this._renderAxis(this.element),this._handles=e(".ui-resizable-handle",this.element).disableSelection(),this._handles.mouseover(function(){o.resizing||(this.className&&(n=this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i)),o.axis=n&&n[1]?n[1]:"se")}),r.autoHide&&(this._handles.hide(),e(this.element).addClass("ui-resizable-autohide").mouseenter(function(){r.disabled||(e(this).removeClass("ui-resizable-autohide"),o._handles.show())}).mouseleave(function(){r.disabled||o.resizing||(e(this).addClass("ui-resizable-autohide"),o._handles.hide())})),this._mouseInit()},_destroy:function(){this._mouseDestroy();var t,i=function(t){e(t).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing").removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove()};return this.elementIsWrapper&&(i(this.element),t=this.element,this.originalElement.css({position:t.css("position"),width:t.outerWidth(),height:t.outerHeight(),top:t.css("top"),left:t.css("left")}).insertAfter(t),t.remove()),this.originalElement.css("resize",this.originalResizeStyle),i(this.originalElement),this},_mouseCapture:function(t){var i,s,n=!1;for(i in this.handles)s=e(this.handles[i])[0],(s===t.target||e.contains(s,t.target))&&(n=!0);return!this.options.disabled&&n},_mouseStart:function(i){var s,n,a,o=this.options,r=this.element.position(),h=this.element;return this.resizing=!0,/absolute/.test(h.css("position"))?h.css({position:"absolute",top:h.css("top"),left:h.css("left")}):h.is(".ui-draggable")&&h.css({position:"absolute",top:r.top,left:r.left}),this._renderProxy(),s=t(this.helper.css("left")),n=t(this.helper.css("top")),o.containment&&(s+=e(o.containment).scrollLeft()||0,n+=e(o.containment).scrollTop()||0),this.offset=this.helper.offset(),this.position={left:s,top:n},this.size=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalSize=this._helper?{width:h.outerWidth(),height:h.outerHeight()}:{width:h.width(),height:h.height()},this.originalPosition={left:s,top:n},this.sizeDiff={width:h.outerWidth()-h.width(),height:h.outerHeight()-h.height()},this.originalMousePosition={left:i.pageX,top:i.pageY},this.aspectRatio="number"==typeof o.aspectRatio?o.aspectRatio:this.originalSize.width/this.originalSize.height||1,a=e(".ui-resizable-"+this.axis).css("cursor"),e("body").css("cursor","auto"===a?this.axis+"-resize":a),h.addClass("ui-resizable-resizing"),this._propagate("start",i),!0},_mouseDrag:function(t){var i,s=this.helper,n={},a=this.originalMousePosition,o=this.axis,r=this.position.top,h=this.position.left,l=this.size.width,u=this.size.height,c=t.pageX-a.left||0,d=t.pageY-a.top||0,p=this._change[o];return p?(i=p.apply(this,[t,c,d]),this._updateVirtualBoundaries(t.shiftKey),(this._aspectRatio||t.shiftKey)&&(i=this._updateRatio(i,t)),i=this._respectSize(i,t),this._updateCache(i),this._propagate("resize",t),this.position.top!==r&&(n.top=this.position.top+"px"),this.position.left!==h&&(n.left=this.position.left+"px"),this.size.width!==l&&(n.width=this.size.width+"px"),this.size.height!==u&&(n.height=this.size.height+"px"),s.css(n),!this._helper&&this._proportionallyResizeElements.length&&this._proportionallyResize(),e.isEmptyObject(n)||this._trigger("resize",t,this.ui()),!1):!1},_mouseStop:function(t){this.resizing=!1;var i,s,n,a,o,r,h,l=this.options,u=this;return this._helper&&(i=this._proportionallyResizeElements,s=i.length&&/textarea/i.test(i[0].nodeName),n=s&&e.ui.hasScroll(i[0],"left")?0:u.sizeDiff.height,a=s?0:u.sizeDiff.width,o={width:u.helper.width()-a,height:u.helper.height()-n},r=parseInt(u.element.css("left"),10)+(u.position.left-u.originalPosition.left)||null,h=parseInt(u.element.css("top"),10)+(u.position.top-u.originalPosition.top)||null,l.animate||this.element.css(e.extend(o,{top:h,left:r})),u.helper.height(u.size.height),u.helper.width(u.size.width),this._helper&&!l.animate&&this._proportionallyResize()),e("body").css("cursor","auto"),this.element.removeClass("ui-resizable-resizing"),this._propagate("stop",t),this._helper&&this.helper.remove(),!1},_updateVirtualBoundaries:function(e){var t,s,n,a,o,r=this.options;o={minWidth:i(r.minWidth)?r.minWidth:0,maxWidth:i(r.maxWidth)?r.maxWidth:1/0,minHeight:i(r.minHeight)?r.minHeight:0,maxHeight:i(r.maxHeight)?r.maxHeight:1/0},(this._aspectRatio||e)&&(t=o.minHeight*this.aspectRatio,n=o.minWidth/this.aspectRatio,s=o.maxHeight*this.aspectRatio,a=o.maxWidth/this.aspectRatio,t>o.minWidth&&(o.minWidth=t),n>o.minHeight&&(o.minHeight=n),o.maxWidth>s&&(o.maxWidth=s),o.maxHeight>a&&(o.maxHeight=a)),this._vBoundaries=o},_updateCache:function(e){this.offset=this.helper.offset(),i(e.left)&&(this.position.left=e.left),i(e.top)&&(this.position.top=e.top),i(e.height)&&(this.size.height=e.height),i(e.width)&&(this.size.width=e.width)},_updateRatio:function(e){var t=this.position,s=this.size,n=this.axis;return i(e.height)?e.width=e.height*this.aspectRatio:i(e.width)&&(e.height=e.width/this.aspectRatio),"sw"===n&&(e.left=t.left+(s.width-e.width),e.top=null),"nw"===n&&(e.top=t.top+(s.height-e.height),e.left=t.left+(s.width-e.width)),e},_respectSize:function(e){var t=this._vBoundaries,s=this.axis,n=i(e.width)&&t.maxWidth&&t.maxWidthe.width,r=i(e.height)&&t.minHeight&&t.minHeight>e.height,h=this.originalPosition.left+this.originalSize.width,l=this.position.top+this.size.height,u=/sw|nw|w/.test(s),c=/nw|ne|n/.test(s);return o&&(e.width=t.minWidth),r&&(e.height=t.minHeight),n&&(e.width=t.maxWidth),a&&(e.height=t.maxHeight),o&&u&&(e.left=h-t.minWidth),n&&u&&(e.left=h-t.maxWidth),r&&c&&(e.top=l-t.minHeight),a&&c&&(e.top=l-t.maxHeight),e.width||e.height||e.left||!e.top?e.width||e.height||e.top||!e.left||(e.left=null):e.top=null,e},_proportionallyResize:function(){if(this._proportionallyResizeElements.length){var e,t,i,s,n,a=this.helper||this.element;for(e=0;this._proportionallyResizeElements.length>e;e++){if(n=this._proportionallyResizeElements[e],!this.borderDif)for(this.borderDif=[],i=[n.css("borderTopWidth"),n.css("borderRightWidth"),n.css("borderBottomWidth"),n.css("borderLeftWidth")],s=[n.css("paddingTop"),n.css("paddingRight"),n.css("paddingBottom"),n.css("paddingLeft")],t=0;i.length>t;t++)this.borderDif[t]=(parseInt(i[t],10)||0)+(parseInt(s[t],10)||0);n.css({height:a.height()-this.borderDif[0]-this.borderDif[2]||0,width:a.width()-this.borderDif[1]-this.borderDif[3]||0})}}},_renderProxy:function(){var t=this.element,i=this.options;this.elementOffset=t.offset(),this._helper?(this.helper=this.helper||e("
"),this.helper.addClass(this._helper).css({width:this.element.outerWidth()-1,height:this.element.outerHeight()-1,position:"absolute",left:this.elementOffset.left+"px",top:this.elementOffset.top+"px",zIndex:++i.zIndex}),this.helper.appendTo("body").disableSelection()):this.helper=this.element},_change:{e:function(e,t){return{width:this.originalSize.width+t}},w:function(e,t){var i=this.originalSize,s=this.originalPosition;return{left:s.left+t,width:i.width-t}},n:function(e,t,i){var s=this.originalSize,n=this.originalPosition;return{top:n.top+i,height:s.height-i}},s:function(e,t,i){return{height:this.originalSize.height+i}},se:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},sw:function(t,i,s){return e.extend(this._change.s.apply(this,arguments),this._change.w.apply(this,[t,i,s]))},ne:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.e.apply(this,[t,i,s]))},nw:function(t,i,s){return e.extend(this._change.n.apply(this,arguments),this._change.w.apply(this,[t,i,s]))}},_propagate:function(t,i){e.ui.plugin.call(this,t,[i,this.ui()]),"resize"!==t&&this._trigger(t,i,this.ui())},plugins:{},ui:function(){return{originalElement:this.originalElement,element:this.element,helper:this.helper,position:this.position,size:this.size,originalSize:this.originalSize,originalPosition:this.originalPosition}}}),e.ui.plugin.add("resizable","animate",{stop:function(t){var i=e(this).data("ui-resizable"),s=i.options,n=i._proportionallyResizeElements,a=n.length&&/textarea/i.test(n[0].nodeName),o=a&&e.ui.hasScroll(n[0],"left")?0:i.sizeDiff.height,r=a?0:i.sizeDiff.width,h={width:i.size.width-r,height:i.size.height-o},l=parseInt(i.element.css("left"),10)+(i.position.left-i.originalPosition.left)||null,u=parseInt(i.element.css("top"),10)+(i.position.top-i.originalPosition.top)||null;i.element.animate(e.extend(h,u&&l?{top:u,left:l}:{}),{duration:s.animateDuration,easing:s.animateEasing,step:function(){var s={width:parseInt(i.element.css("width"),10),height:parseInt(i.element.css("height"),10),top:parseInt(i.element.css("top"),10),left:parseInt(i.element.css("left"),10)};n&&n.length&&e(n[0]).css({width:s.width,height:s.height}),i._updateCache(s),i._propagate("resize",t)}})}}),e.ui.plugin.add("resizable","containment",{start:function(){var i,s,n,a,o,r,h,l=e(this).data("ui-resizable"),u=l.options,c=l.element,d=u.containment,p=d instanceof e?d.get(0):/parent/.test(d)?c.parent().get(0):d;p&&(l.containerElement=e(p),/document/.test(d)||d===document?(l.containerOffset={left:0,top:0},l.containerPosition={left:0,top:0},l.parentData={element:e(document),left:0,top:0,width:e(document).width(),height:e(document).height()||document.body.parentNode.scrollHeight}):(i=e(p),s=[],e(["Top","Right","Left","Bottom"]).each(function(e,n){s[e]=t(i.css("padding"+n))}),l.containerOffset=i.offset(),l.containerPosition=i.position(),l.containerSize={height:i.innerHeight()-s[3],width:i.innerWidth()-s[1]},n=l.containerOffset,a=l.containerSize.height,o=l.containerSize.width,r=e.ui.hasScroll(p,"left")?p.scrollWidth:o,h=e.ui.hasScroll(p)?p.scrollHeight:a,l.parentData={element:p,left:n.left,top:n.top,width:r,height:h}))},resize:function(t){var i,s,n,a,o=e(this).data("ui-resizable"),r=o.options,h=o.containerOffset,l=o.position,u=o._aspectRatio||t.shiftKey,c={top:0,left:0},d=o.containerElement;d[0]!==document&&/static/.test(d.css("position"))&&(c=h),l.left<(o._helper?h.left:0)&&(o.size.width=o.size.width+(o._helper?o.position.left-h.left:o.position.left-c.left),u&&(o.size.height=o.size.width/o.aspectRatio),o.position.left=r.helper?h.left:0),l.top<(o._helper?h.top:0)&&(o.size.height=o.size.height+(o._helper?o.position.top-h.top:o.position.top),u&&(o.size.width=o.size.height*o.aspectRatio),o.position.top=o._helper?h.top:0),o.offset.left=o.parentData.left+o.position.left,o.offset.top=o.parentData.top+o.position.top,i=Math.abs((o._helper?o.offset.left-c.left:o.offset.left-c.left)+o.sizeDiff.width),s=Math.abs((o._helper?o.offset.top-c.top:o.offset.top-h.top)+o.sizeDiff.height),n=o.containerElement.get(0)===o.element.parent().get(0),a=/relative|absolute/.test(o.containerElement.css("position")),n&&a&&(i-=o.parentData.left),i+o.size.width>=o.parentData.width&&(o.size.width=o.parentData.width-i,u&&(o.size.height=o.size.width/o.aspectRatio)),s+o.size.height>=o.parentData.height&&(o.size.height=o.parentData.height-s,u&&(o.size.width=o.size.height*o.aspectRatio))},stop:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.containerOffset,n=t.containerPosition,a=t.containerElement,o=e(t.helper),r=o.offset(),h=o.outerWidth()-t.sizeDiff.width,l=o.outerHeight()-t.sizeDiff.height;t._helper&&!i.animate&&/relative/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l}),t._helper&&!i.animate&&/static/.test(a.css("position"))&&e(this).css({left:r.left-n.left-s.left,width:h,height:l})}}),e.ui.plugin.add("resizable","alsoResize",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,s=function(t){e(t).each(function(){var t=e(this);t.data("ui-resizable-alsoresize",{width:parseInt(t.width(),10),height:parseInt(t.height(),10),left:parseInt(t.css("left"),10),top:parseInt(t.css("top"),10)})})};"object"!=typeof i.alsoResize||i.alsoResize.parentNode?s(i.alsoResize):i.alsoResize.length?(i.alsoResize=i.alsoResize[0],s(i.alsoResize)):e.each(i.alsoResize,function(e){s(e)})},resize:function(t,i){var s=e(this).data("ui-resizable"),n=s.options,a=s.originalSize,o=s.originalPosition,r={height:s.size.height-a.height||0,width:s.size.width-a.width||0,top:s.position.top-o.top||0,left:s.position.left-o.left||0},h=function(t,s){e(t).each(function(){var t=e(this),n=e(this).data("ui-resizable-alsoresize"),a={},o=s&&s.length?s:t.parents(i.originalElement[0]).length?["width","height"]:["width","height","top","left"];e.each(o,function(e,t){var i=(n[t]||0)+(r[t]||0);i&&i>=0&&(a[t]=i||null)}),t.css(a)})};"object"!=typeof n.alsoResize||n.alsoResize.nodeType?h(n.alsoResize):e.each(n.alsoResize,function(e,t){h(e,t)})},stop:function(){e(this).removeData("resizable-alsoresize")}}),e.ui.plugin.add("resizable","ghost",{start:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.size;t.ghost=t.originalElement.clone(),t.ghost.css({opacity:.25,display:"block",position:"relative",height:s.height,width:s.width,margin:0,left:0,top:0}).addClass("ui-resizable-ghost").addClass("string"==typeof i.ghost?i.ghost:""),t.ghost.appendTo(t.helper)},resize:function(){var t=e(this).data("ui-resizable");t.ghost&&t.ghost.css({position:"relative",height:t.size.height,width:t.size.width})},stop:function(){var t=e(this).data("ui-resizable");t.ghost&&t.helper&&t.helper.get(0).removeChild(t.ghost.get(0))}}),e.ui.plugin.add("resizable","grid",{resize:function(){var t=e(this).data("ui-resizable"),i=t.options,s=t.size,n=t.originalSize,a=t.originalPosition,o=t.axis,r="number"==typeof i.grid?[i.grid,i.grid]:i.grid,h=r[0]||1,l=r[1]||1,u=Math.round((s.width-n.width)/h)*h,c=Math.round((s.height-n.height)/l)*l,d=n.width+u,p=n.height+c,f=i.maxWidth&&d>i.maxWidth,m=i.maxHeight&&p>i.maxHeight,g=i.minWidth&&i.minWidth>d,v=i.minHeight&&i.minHeight>p;i.grid=r,g&&(d+=h),v&&(p+=l),f&&(d-=h),m&&(p-=l),/^(se|s|e)$/.test(o)?(t.size.width=d,t.size.height=p):/^(ne)$/.test(o)?(t.size.width=d,t.size.height=p,t.position.top=a.top-c):/^(sw)$/.test(o)?(t.size.width=d,t.size.height=p,t.position.left=a.left-u):(t.size.width=d,t.size.height=p,t.position.top=a.top-c,t.position.left=a.left-u)}})})(jQuery); ================================================ FILE: talks/static/slides.js ================================================ // Copyright 2012 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. var PERMANENT_URL_PREFIX = '/static/'; var SLIDE_CLASSES = ['far-past', 'past', 'current', 'next', 'far-next']; var PM_TOUCH_SENSITIVITY = 15; var curSlide; /* ---------------------------------------------------------------------- */ /* classList polyfill by Eli Grey * (http://purl.eligrey.com/github/classList.js/blob/master/classList.js) */ if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) { (function (view) { var classListProp = "classList" , protoProp = "prototype" , elemCtrProto = (view.HTMLElement || view.Element)[protoProp] , objCtr = Object strTrim = String[protoProp].trim || function () { return this.replace(/^\s+|\s+$/g, ""); } , arrIndexOf = Array[protoProp].indexOf || function (item) { for (var i = 0, len = this.length; i < len; i++) { if (i in this && this[i] === item) { return i; } } return -1; } // Vendors: please allow content code to instantiate DOMExceptions , DOMEx = function (type, message) { this.name = type; this.code = DOMException[type]; this.message = message; } , checkTokenAndGetIndex = function (classList, token) { if (token === "") { throw new DOMEx( "SYNTAX_ERR" , "An invalid or illegal string was specified" ); } if (/\s/.test(token)) { throw new DOMEx( "INVALID_CHARACTER_ERR" , "String contains an invalid character" ); } return arrIndexOf.call(classList, token); } , ClassList = function (elem) { var trimmedClasses = strTrim.call(elem.className) , classes = trimmedClasses ? trimmedClasses.split(/\s+/) : [] ; for (var i = 0, len = classes.length; i < len; i++) { this.push(classes[i]); } this._updateClassName = function () { elem.className = this.toString(); }; } , classListProto = ClassList[protoProp] = [] , classListGetter = function () { return new ClassList(this); } ; // Most DOMException implementations don't allow calling DOMException's toString() // on non-DOMExceptions. Error's toString() is sufficient here. DOMEx[protoProp] = Error[protoProp]; classListProto.item = function (i) { return this[i] || null; }; classListProto.contains = function (token) { token += ""; return checkTokenAndGetIndex(this, token) !== -1; }; classListProto.add = function (token) { token += ""; if (checkTokenAndGetIndex(this, token) === -1) { this.push(token); this._updateClassName(); } }; classListProto.remove = function (token) { token += ""; var index = checkTokenAndGetIndex(this, token); if (index !== -1) { this.splice(index, 1); this._updateClassName(); } }; classListProto.toggle = function (token) { token += ""; if (checkTokenAndGetIndex(this, token) === -1) { this.add(token); } else { this.remove(token); } }; classListProto.toString = function () { return this.join(" "); }; if (objCtr.defineProperty) { var classListPropDesc = { get: classListGetter , enumerable: true , configurable: true }; try { objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } catch (ex) { // IE 8 doesn't support enumerable:true if (ex.number === -0x7FF5EC54) { classListPropDesc.enumerable = false; objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc); } } } else if (objCtr[protoProp].__defineGetter__) { elemCtrProto.__defineGetter__(classListProp, classListGetter); } }(self)); } /* ---------------------------------------------------------------------- */ /* Slide movement */ function hideHelpText() { $('#help').hide(); }; function getSlideEl(no) { if ((no < 0) || (no >= slideEls.length)) { return null; } else { return slideEls[no]; } }; function updateSlideClass(slideNo, className) { var el = getSlideEl(slideNo); if (!el) { return; } if (className) { el.classList.add(className); } for (var i in SLIDE_CLASSES) { if (className != SLIDE_CLASSES[i]) { el.classList.remove(SLIDE_CLASSES[i]); } } }; function updateSlides() { if (window.trackPageview) window.trackPageview(); for (var i = 0; i < slideEls.length; i++) { switch (i) { case curSlide - 2: updateSlideClass(i, 'far-past'); break; case curSlide - 1: updateSlideClass(i, 'past'); break; case curSlide: updateSlideClass(i, 'current'); break; case curSlide + 1: updateSlideClass(i, 'next'); break; case curSlide + 2: updateSlideClass(i, 'far-next'); break; default: updateSlideClass(i); break; } } triggerLeaveEvent(curSlide - 1); triggerEnterEvent(curSlide); window.setTimeout(function() { // Hide after the slide disableSlideFrames(curSlide - 2); }, 301); enableSlideFrames(curSlide - 1); enableSlideFrames(curSlide + 2); updateHash(); }; function prevSlide() { hideHelpText(); if (curSlide > 0) { curSlide--; updateSlides(); } }; function nextSlide() { hideHelpText(); if (curSlide < slideEls.length - 1) { curSlide++; updateSlides(); } }; /* Slide events */ function triggerEnterEvent(no) { var el = getSlideEl(no); if (!el) { return; } var onEnter = el.getAttribute('onslideenter'); if (onEnter) { new Function(onEnter).call(el); } var evt = document.createEvent('Event'); evt.initEvent('slideenter', true, true); evt.slideNumber = no + 1; // Make it readable el.dispatchEvent(evt); }; function triggerLeaveEvent(no) { var el = getSlideEl(no); if (!el) { return; } var onLeave = el.getAttribute('onslideleave'); if (onLeave) { new Function(onLeave).call(el); } var evt = document.createEvent('Event'); evt.initEvent('slideleave', true, true); evt.slideNumber = no + 1; // Make it readable el.dispatchEvent(evt); }; /* Touch events */ function handleTouchStart(event) { if (event.touches.length == 1) { touchDX = 0; touchDY = 0; touchStartX = event.touches[0].pageX; touchStartY = event.touches[0].pageY; document.body.addEventListener('touchmove', handleTouchMove, true); document.body.addEventListener('touchend', handleTouchEnd, true); } }; function handleTouchMove(event) { if (event.touches.length > 1) { cancelTouch(); } else { touchDX = event.touches[0].pageX - touchStartX; touchDY = event.touches[0].pageY - touchStartY; event.preventDefault(); } }; function handleTouchEnd(event) { var dx = Math.abs(touchDX); var dy = Math.abs(touchDY); if ((dx > PM_TOUCH_SENSITIVITY) && (dy < (dx * 2 / 3))) { if (touchDX > 0) { prevSlide(); } else { nextSlide(); } } cancelTouch(); }; function cancelTouch() { document.body.removeEventListener('touchmove', handleTouchMove, true); document.body.removeEventListener('touchend', handleTouchEnd, true); }; /* Preloading frames */ function disableSlideFrames(no) { var el = getSlideEl(no); if (!el) { return; } var frames = el.getElementsByTagName('iframe'); for (var i = 0, frame; frame = frames[i]; i++) { disableFrame(frame); } }; function enableSlideFrames(no) { var el = getSlideEl(no); if (!el) { return; } var frames = el.getElementsByTagName('iframe'); for (var i = 0, frame; frame = frames[i]; i++) { enableFrame(frame); } }; function disableFrame(frame) { frame.src = 'about:blank'; }; function enableFrame(frame) { var src = frame._src; if (frame.src != src && src != 'about:blank') { frame.src = src; } }; function setupFrames() { var frames = document.querySelectorAll('iframe'); for (var i = 0, frame; frame = frames[i]; i++) { frame._src = frame.src; disableFrame(frame); } enableSlideFrames(curSlide); enableSlideFrames(curSlide + 1); enableSlideFrames(curSlide + 2); }; function setupInteraction() { /* Clicking and tapping */ var el = document.createElement('div'); el.className = 'slide-area'; el.id = 'prev-slide-area'; el.addEventListener('click', prevSlide, false); document.querySelector('section.slides').appendChild(el); var el = document.createElement('div'); el.className = 'slide-area'; el.id = 'next-slide-area'; el.addEventListener('click', nextSlide, false); document.querySelector('section.slides').appendChild(el); /* Swiping */ document.body.addEventListener('touchstart', handleTouchStart, false); } /* Hash functions */ function getCurSlideFromHash() { var slideNo = parseInt(location.hash.substr(1)); if (slideNo) { curSlide = slideNo - 1; } else { curSlide = 0; } }; function updateHash() { location.replace('#' + (curSlide + 1)); }; /* Event listeners */ function handleBodyKeyDown(event) { // If we're in a code element, only handle pgup/down. var inCode = event.target.classList.contains("code"); switch (event.keyCode) { case 72: // 'H' hides the help text case 27: // escape key if (!inCode) hideHelpText(); break; case 39: // right arrow case 13: // Enter case 32: // space if (inCode) break; case 34: // PgDn nextSlide(); event.preventDefault(); break; case 37: // left arrow case 8: // Backspace if (inCode) break; case 33: // PgUp prevSlide(); event.preventDefault(); break; case 40: // down arrow if (inCode) break; nextSlide(); event.preventDefault(); break; case 38: // up arrow if (inCode) break; prevSlide(); event.preventDefault(); break; } }; function addEventListeners() { document.addEventListener('keydown', handleBodyKeyDown, false); }; /* Initialization */ function addFontStyle() { var el = document.createElement('link'); el.rel = 'stylesheet'; el.type = 'text/css'; el.href = '//fonts.googleapis.com/css?family=' + 'Open+Sans:regular,semibold,italic,italicsemibold|Droid+Sans+Mono'; document.body.appendChild(el); }; function addGeneralStyle() { var el = document.createElement('link'); el.rel = 'stylesheet'; el.type = 'text/css'; el.href = PERMANENT_URL_PREFIX + 'styles.css'; document.body.appendChild(el); var el = document.createElement('meta'); el.name = 'viewport'; el.content = 'width=1100,height=750'; document.querySelector('head').appendChild(el); var el = document.createElement('meta'); el.name = 'apple-mobile-web-app-capable'; el.content = 'yes'; document.querySelector('head').appendChild(el); }; function showHelpText() { }; function handleDomLoaded() { slideEls = document.querySelectorAll('section.slides > article'); setupFrames(); addFontStyle(); addGeneralStyle(); addEventListeners(); updateSlides(); setupInteraction(); if (window.location.hostname == "localhost" || window.location.hostname == "127.0.0.1" || window.location.hostname == "::1") { hideHelpText(); } document.body.classList.add('loaded'); }; function initialize() { getCurSlideFromHash(); if (window['_DEBUG']) { PERMANENT_URL_PREFIX = '../'; } if (window['_DCL']) { handleDomLoaded(); } else { document.addEventListener('DOMContentLoaded', handleDomLoaded, false); } } // If ?debug exists then load the script relative instead of absolute if (!window['_DEBUG'] && document.location.href.indexOf('?debug') !== -1) { document.addEventListener('DOMContentLoaded', function() { // Avoid missing the DomContentLoaded event window['_DCL'] = true }, false); window['_DEBUG'] = true; var script = document.createElement('script'); script.type = 'text/javascript'; script.src = '../slides.js'; var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(script, s); // Remove this script s.parentNode.removeChild(s); } else { initialize(); } ================================================ FILE: talks/static/styles.css ================================================ @media screen { /* Framework */ html { height: 100%; } body { margin: 0; padding: 0; display: block !important; height: 100%; min-height: 740px; overflow-x: hidden; overflow-y: auto; background: rgb(215, 215, 215); background: -o-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); background: -moz-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); background: -webkit-radial-gradient(rgb(240, 240, 240), rgb(190, 190, 190)); background: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 500, from(rgb(240, 240, 240)), to(rgb(190, 190, 190))); -webkit-font-smoothing: antialiased; } .slides { width: 100%; height: 100%; left: 0; top: 0; position: absolute; -webkit-transform: translate3d(0, 0, 0); } .slides > article { display: block; position: absolute; overflow: hidden; width: 900px; height: 700px; left: 50%; top: 50%; margin-left: -450px; margin-top: -350px; padding: 40px 60px; box-sizing: border-box; -o-box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; border-radius: 10px; -o-border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; background-color: white; background-image: url(/static/img/background.png); background-position: right; background-size: cover; border: 1px solid rgba(0, 0, 0, .3); transition: transform .3s ease-out; -o-transition: -o-transform .3s ease-out; -moz-transition: -moz-transform .3s ease-out; -webkit-transition: -webkit-transform .3s ease-out; } .slides.layout-widescreen > article { margin-left: -550px; width: 1100px; } .slides.layout-faux-widescreen > article { margin-left: -550px; width: 1100px; padding: 40px 160px; } .slides.layout-widescreen > article:not(.nobackground):not(.biglogo), .slides.layout-faux-widescreen > article:not(.nobackground):not(.biglogo) { background-position-x: 0, 840px; } /* Clickable/tappable areas */ .slide-area { z-index: 1000; position: absolute; left: 0; top: 0; width: 150px; height: 700px; left: 50%; top: 50%; cursor: pointer; margin-top: -350px; tap-highlight-color: transparent; -o-tap-highlight-color: transparent; -moz-tap-highlight-color: transparent; -webkit-tap-highlight-color: transparent; } #prev-slide-area { margin-left: -550px; } #next-slide-area { margin-left: 400px; } .slides.layout-widescreen #prev-slide-area, .slides.layout-faux-widescreen #prev-slide-area { margin-left: -650px; } .slides.layout-widescreen #next-slide-area, .slides.layout-faux-widescreen #next-slide-area { margin-left: 500px; } /* Slides */ .slides > article { display: none; } .slides > article.far-past { display: block; transform: translate(-2040px); -o-transform: translate(-2040px); -moz-transform: translate(-2040px); -webkit-transform: translate3d(-2040px, 0, 0); } .slides > article.past { display: block; transform: translate(-1020px); -o-transform: translate(-1020px); -moz-transform: translate(-1020px); -webkit-transform: translate3d(-1020px, 0, 0); } .slides > article.current { display: block; transform: translate(0); -o-transform: translate(0); -moz-transform: translate(0); -webkit-transform: translate3d(0, 0, 0); } .slides > article.next { display: block; transform: translate(1020px); -o-transform: translate(1020px); -moz-transform: translate(1020px); -webkit-transform: translate3d(1020px, 0, 0); } .slides > article.far-next { display: block; transform: translate(2040px); -o-transform: translate(2040px); -moz-transform: translate(2040px); -webkit-transform: translate3d(2040px, 0, 0); } .slides.layout-widescreen > article.far-past, .slides.layout-faux-widescreen > article.far-past { display: block; transform: translate(-2260px); -o-transform: translate(-2260px); -moz-transform: translate(-2260px); -webkit-transform: translate3d(-2260px, 0, 0); } .slides.layout-widescreen > article.past, .slides.layout-faux-widescreen > article.past { display: block; transform: translate(-1130px); -o-transform: translate(-1130px); -moz-transform: translate(-1130px); -webkit-transform: translate3d(-1130px, 0, 0); } .slides.layout-widescreen > article.current, .slides.layout-faux-widescreen > article.current { display: block; transform: translate(0); -o-transform: translate(0); -moz-transform: translate(0); -webkit-transform: translate3d(0, 0, 0); } .slides.layout-widescreen > article.next, .slides.layout-faux-widescreen > article.next { display: block; transform: translate(1130px); -o-transform: translate(1130px); -moz-transform: translate(1130px); -webkit-transform: translate3d(1130px, 0, 0); } .slides.layout-widescreen > article.far-next, .slides.layout-faux-widescreen > article.far-next { display: block; transform: translate(2260px); -o-transform: translate(2260px); -moz-transform: translate(2260px); -webkit-transform: translate3d(2260px, 0, 0); } } @media print { /* Set page layout */ @page { size: A4 landscape; } body { display: block !important; } .slides > article { display: block; position: relative; page-break-inside: never; page-break-after: always; overflow: hidden; } h2 { position: static !important; margin-top: 400px !important; margin-bottom: 100px !important; } div.code { background: rgb(240, 240, 240); } /* Add explicit links */ a:link:after, a:visited:after { content: " (" attr(href) ") "; font-size: 50%; } #help { display: none; visibility: hidden; } } /* Styles for slides */ .slides > article { font-family: 'Open Sans', Arial, sans-serif; color: black; text-shadow: 0 1px 1px rgba(0, 0, 0, .1); font-size: 26px; line-height: 36px; letter-spacing: -1px; } b { font-weight: 600; } a { color: rgb(0, 102, 204); text-decoration: none; } a:visited { color: rgba(0, 102, 204, .75); } a:hover { color: black; } p { margin: 0; padding: 0; margin-top: 20px; } p:first-child { margin-top: 0; } h1 { font-size: 60px; line-height: 60px; padding: 0; margin: 0; margin-top: 200px; margin-bottom: 5px; padding-right: 40px; font-weight: 600; letter-spacing: -3px; color: rgb(51, 51, 51); } h2 { font-size: 45px; line-height: 45px; position: absolute; bottom: 150px; padding: 0; margin: 0; padding-right: 40px; font-weight: 600; letter-spacing: -2px; color: rgb(51, 51, 51); } h3 { font-size: 30px; line-height: 36px; padding: 0; margin: 0; padding-right: 40px; font-weight: 600; letter-spacing: -1px; color: rgb(51, 51, 51); } ul { margin: 0; padding: 0; margin-top: 20px; margin-left: 1.5em; } li { padding: 0; margin: 0 0 .5em 0; } div.code { padding: 5px 10px; margin-top: 20px; margin-bottom: 20px; overflow: hidden; background: rgb(240, 240, 240); border: 1px solid rgb(224, 224, 224); } pre { margin: 0; padding: 0; font-family: 'Droid Sans Mono', 'Courier New', monospace; font-size: 18px; line-height: 24px; letter-spacing: -1px; color: black; } code { font-size: 95%; font-family: 'Droid Sans Mono', 'Courier New', monospace; color: black; } article > .image, article > .video { text-align: center; margin-top: 40px; } article > .background { position: absolute; top: 0; left: 0; right: 0; bottom: 0; z-index: -1; } article > .background > img { max-height: 100%; max-width: 100%; } table { width: 100%; border-collapse: collapse; margin-top: 40px; } th { font-weight: 600; text-align: left; } td, th { border: 1px solid rgb(224, 224, 224); padding: 5px 10px; vertical-align: top; } p.link { margin-left: 20px; } /* Code */ div.code { outline: 0px solid transparent; } div.playground { position: relative; } div.output { position: absolute; left: 50%; top: 50%; right: 40px; bottom: 40px; background: #202020; padding: 5px 10px; z-index: 2; border-radius: 10px; -o-border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; } div.output pre { margin: 0; padding: 0; background: none; border: none; width: 100%; height: 100%; overflow: auto; } div.output .stdout, div.output pre { color: #e6e6e6; } div.output .stderr, div.output .error { color: rgb(255, 200, 200); } div.output .system, div.output .exit { color: rgb(255, 230, 120) } .buttons { position: relative; float: right; top: -60px; right: 10px; } div.output .buttons { position: absolute; float: none; top: auto; right: 5px; bottom: 5px; } /* Presenter details */ .presenter { margin-top: 20px; } .presenter p, .presenter .link { margin: 0; font-size: 28px; line-height: 1.2em; } /* Output resize details */ .ui-resizable-handle { position: absolute; } .ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } .ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } .ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } iframe { border: none; } figcaption { color: #666; text-align: center; font-size: 0.75em; } #help { font-family: 'Open Sans', Arial, sans-serif; text-align: center; color: white; background: #000; opacity: 0.5; position: fixed; bottom: 25px; left: 50px; right: 50px; padding: 20px; border-radius: 10px; -o-border-radius: 10px; -moz-border-radius: 10px; -webkit-border-radius: 10px; } header > .icl-logo { position: absolute; left: 50px; top: 50px; background-image: url(/static/img/icl-logo.jpg); background-position: top left; background-size: auto 40px; background-repeat: no-repeat; height: 40px; width: 160px; } header > .mrg-logo { position: absolute; right: 50px; top: 50px; background-image: url(/static/img/mrg-logo.png); background-position: top right; background-size: auto 40px; background-repeat: no-repeat; height: 40px; width: 40px; } footer { position: absolute; bottom: 0; left: 0; font-size: medium; width: 100%; text-align: center; } footer > .page-number { position: absolute; right: 10px; bottom: 10px; vertical-align: bottom; height: 24px; } footer > .mrg-logo { position: absolute; left: 10px; bottom: 10px; background-image: url(/static/img/mrg-logo.png); background-position: top left; background-repeat: no-repeat; background-size: 24px 24px; height: 24px; min-width: 24px; } footer > .mrg-logo:after { height: 24px; font-size: small; vertical-align: top; padding-left: 30px; color: #333; content: 'http://mrg.doc.ic.ac.uk'; } a[href='#colour:blue'] { color: darkblue; cursor: default; } a[href='#colour:red'] { color: darkred; cursor: default; } a[href='#colour:green'] { color: green; cursor: default; } div.title-img { background-position: center; background-repeat: no-repeat; background-image: url(/conf-cc-2016/idea.png); width: 100%; height: 400px; } ================================================ FILE: talks/talk-golanguk-2016/calculi.html ================================================

Tony Hoare
  • Quicksort, Hoare Logic, etc.
  • CSP - Communicating Sequential Processes (1978)
  • Turing Award (1980)



Robin Milner
  • ML programming language, LCF, etc.
  • CCS - Calculus of Communicating Systems (1980)
  • π-calculus (1992)
  • Turing Award (1991)
================================================ FILE: talks/talk-golanguk-2016/compat.html ================================================
================================================ FILE: talks/talk-golanguk-2016/datatype.go ================================================ // +build OMIT package main import ( "fmt" ) func main() { x := 42 fmt.Printf("Type of x is %T", x) x = "hello" // incompatible! } ================================================ FILE: talks/talk-golanguk-2016/deadlock-notdetected.go ================================================ // +build OMIT package main import ( "fmt" "time" ) func Sender(ch chan<- int) { ch <- 42 } func Receiver(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch := make(chan int) done := make(chan int) go Sender(ch) go Receiver(ch, done) go Receiver(ch, done) // Who is ch receiving from? // HLoops // Unreleated worker goroutine, keeps deadlock 'alive' go func() { // HLwork for i := 0; i < 2; i++ { // HLwork fmt.Println("Working #", i) // HLwork time.Sleep(500 * time.Millisecond) // HLwork } // HLwork fmt.Println("-------- Worker finished --------") // HLwork }() // HLwork fmt.Println("Done 1:", <-done) fmt.Println("Done 2:", <-done) // HLoops } ================================================ FILE: talks/talk-golanguk-2016/deadlock.go ================================================ // +build OMIT package main import ( "fmt" "time" ) // Anonymous goroutine: Some long running work (e.g. http service) func Work() { for i := 0; ; i++ { fmt.Println("Working #", i) time.Sleep(1 * time.Second) } } // START OMIT func Sender(ch chan<- int) { ch <- 42 } func Receiver(ch <-chan int, done chan<- int) { done <- <-ch } func main() { ch := make(chan int) done := make(chan int) go Sender(ch) go Receiver(ch, done) go Receiver(ch, done) // Who is ch receiving from? // HLoops fmt.Println("Done 1:", <-done) fmt.Println("Done 2:", <-done) // HLoops } // END OMIT ================================================ FILE: talks/talk-golanguk-2016/main-type.html ================================================
func main() {
    ch := make(chan int)
    done := make(chan int)

    go Sender(ch)
    go Receiver(ch, done)
    go Receiver(ch, done) // Who is ch receiving from?
    go Work() // Just an infinite loop

    fmt.Println("Done 1:", <-done)
    fmt.Println("Done 2:", <-done)
}
Session types for main()
================================================ FILE: talks/talk-golanguk-2016/proc.txt ================================================ // +build OMIT func checkdead() { ... // -1 for sysmon run := sched.mcount - sched.nmidle - sched.nmidlelocked - 1 // HL if run > 0 { return } ... getg().m.throwing = -1 // do not dump full stacks throw("all goroutines are asleep - deadlock!") } ================================================ FILE: talks/talk-golanguk-2016/receiver-type.html ================================================
func Receiver(ch <-chan int, done chan<- int) {
    done <- <-ch
}
================================================ FILE: talks/talk-golanguk-2016/sender-type.html ================================================
func Sender(ch chan<- int) {
    ch <- 42
}
================================================ FILE: talks/talk-golanguk-2016/video.html ================================================ ================================================ FILE: talks/talk-golanguk-2016/work-type.html ================================================
func() {          
    for i := 0; i < 4; i++ {
        fmt.Println("Working #", i)       
        time.Sleep(250 * time.Millisecond)
    }
}
================================================ FILE: talks/talk-golanguk-2016.slide ================================================ Static Deadlock Detection for Go Nicholas Ng Imperial College London @nicholascwng nickng@imperial.ac.uk http://mrg.doc.ic.ac.uk * Go and concurrency *Don't*communicate*by*sharing*memory,*share*memory*by*communicating.* Avoid locks for high-level concurrency - *Message-passing* over channels - Coordinate and synchronise goroutines Inspired by Hoare's Communicating Sequential Processes (CSP) Expressive message-passing concurrency model * Concurrency Concurrency is complicated Concurrency bugs are hard to find and fix (write perfect code if you can!) Our research aims to help _avoid_ concurrency bugs as much as possible * Concurrency problems: deadlocks *fatal*error:*all*goroutines*are*asleep*-*deadlock!* .play talk-golanguk-2016/deadlock.go /START OMIT/,/END OMIT/ HLoops * What causes a deadlock in Go? - Sender sends message, Receiver not ready - Sender blocks goroutine and wait until Receiver ready - Blocked goroutine goes to sleep Deadlock - Goroutine blocks waiting for something impossible (e.g. wait for each other) - Whole program gets stuck .image talk-golanguk-2016/deadlock.svg .caption A classic deadlock * Avoiding deadlocks Rule of thumb: *Make*sure*sender/receiver*are*matching*and*compatible* .image talk-golanguk-2016/nodeadlock.svg - e.g. Send vs. Receive - e.g. Range over channel vs. `close` Sounds simple, so why is it difficult in practice? - Communication intertwined with non-communicating code (hint: model) - Program control flow is not a single path from top to bottom * Go runtime deadlock detector - Builtin detector in Go runtime - Count non-blocked goroutines, deadlock if count == 0 - Panic when deadlock is encountered .code talk-golanguk-2016/proc.txt .link https://golang.org/src/runtime/proc.go#L3358 * Go runtime deadlock detector If we add a benign, long-running goroutine to the mix.. .play talk-golanguk-2016/deadlock-notdetected.go /^func main/,/^}/ HLwork _local_deadlock_: some (but not all) goroutines are stuck * Building a better deadlock detector What if we can... - Detect deadlock in program without executing (avoid deadlock) - Find local deadlocks too - Go specific: deadlocks in communication (message passing) * Static deadlock detector Tool developed based on our research .link https://github.com/nickng/dingo-hunter - Static (compile-time) detection of deadlock - Help _prevent_ deadlocks - Ongoing research .image talk-golanguk-2016/qrcode.png Analysed common concurrency patterns & open source projects * Static deadlock detector - Analyse Go *source*code* - Infer model representing communication only - Check if model could get stuck (deadlock) - Safe model, safe code .image talk-golanguk-2016/workflow.svg .caption Under the hood: Workflow of our static deadlock detector * Modelling concurrency To find deadlocks in communication - Accurate representation of communication - Established ideas Who better to ask than pioneers of concurrency? * Models of concurrency: Process calculi Process calculi: canonical models for communication .html talk-golanguk-2016/calculi.html * Models of concurrency: Process calculi Process calculi: canonical models for communication - Formal model of how processes (i.e. goroutines) interact - This is where Go's concurrency model come from - Also what our deadlock detection technique built on .image talk-golanguk-2016/pi.png .caption Essence of communication: asynchronous π-calculus and Go * Models of concurrency: Process calculi Process calculi: canonical models for communication - Does not stop _bad_ interactions (e.g. deadlocks) - Succinct abstractions for communication/concurrency - All we need is a way to check if model is compatible * Types - Go, a typed programming language - Compile-time, static type checking - Is variable _compatible_ with value? (i.e. same type) .play talk-golanguk-2016/datatype.go /^func main/,/^}/ .image talk-golanguk-2016/datatype.svg .caption Type checking: are the types compatible? * Types for communication (1/2) - *Session*Types* - Honda, Vasconcelos, Kubo (1998) - _Multiparty_ Session Types - Honda, Yoshida, Carbone (2008) - Originates from types for π-calculus - Is sender _compatible_ with receiver? .html talk-golanguk-2016/compat.html .caption Compatibility of communication * Types for communication (2/2) - Is sender _compatible_ with receiver? .html talk-golanguk-2016/compat.html .caption Compatibility of communication - Compatible in terms of *communication*structure* - Also take into account _loops_, _if-then-else_ and other control flow elements - So we can _type-check_ process calculi model similarly as ordinary types * How to Go from code to model? - Extract model (session types) from source code - Take into consideration program control flow - Go's amazing tooling ecosystem to the rescue! .link https://www.youtube.com/watch?v=oorX84tBMqo Program Analysis by Francesc Campoy (GolangUK 2015) * Extracting session types from code (1/2) *Static*analysis*: Look for `make(chan`T)`, `ch`<-`x`, `<-ch`, `select`, `close` - Analyse source code using `go/ssa` without executing (flow sensitive) - `go/ssa` makes it easier to understand program control flow main .html talk-golanguk-2016/main-type.html * Extracting session types from code (2/2) Sender .html talk-golanguk-2016/sender-type.html Receiver _x_2_ .html talk-golanguk-2016/receiver-type.html Worker (Non-communicating) .html talk-golanguk-2016/work-type.html * Compose the model - One session type per goroutine - Collect all session types from each goroutine - Are they compatible? .image talk-golanguk-2016/all-local.svg .caption Session types obtained from all goroutines in program Next: compose and check compatibility * Global graph - Combine all session types: a bird's eye view (_global_graph_) of messages - e.g. find matching Send and Receive - Global graph must be well-formed (i.e. satisfy side conditions) .image talk-golanguk-2016/global-graph.svg .caption Incompatible: this is not a well formed global graph Note: the actual analysis is much more involved * Conclusion - Process calculi influenced design of concurrency in Go - Applied current research on process calculi deadlock detection to Go - Hope to inspire more concurrency research *References* 📄 From Communicating Machines to Graphical Choreographies _J._Lange,_E._Tuosto,_N._Yoshida_, POPL 2015 📄 Static Deadlock Detection for Go by Global Session Graph Synthesis _N._Ng,_N._Yoshida_, CC 2016 .caption Thanks to my colleagues behind this work - Nobuko, Julien, Bernardo * Conclusion More to come soon: more comprehensive theory and updated tool .link https://github.com/nickng/dingo-hunter - Unbuffered channels - Limited support for 'dynamic' patterns (e.g. spawn new goroutines while running) *Demo* .html talk-golanguk-2016/video.html ================================================ FILE: talks/talk-jan-2016.slide ================================================ Static Deadlock Detection for Go Nicholas Ng / nicholascwng Imperial College London http://mrg.doc.ic.ac.uk nickng@imperial.ac.uk @nicholascwng * Go Programming Language - Open source - Statically typed - Compiled, systems programming language - Built-in channel-based concurrency * Concurrency in Go - Communicating Sequential Process (CSP) style channel-based concurrency - Composition of sequential code .image talk-jan-2016/csp.png .caption CSP example ("Communicating Sequential Processes", Hoare 2004) * Concurrency in Go: Goroutines and Channels - Goroutines: lightweight threads - Channels: 'link', message-passing between goroutines .play basic/concurrency.go /^func deepThought/,/END OMIT$/ HLsendrecv * Deadlocks in Go - CSP are prone to these - Communication deadlocks: blocked communication (for 1 or more goroutines) fatal error: all goroutines are asleep - deadlock! .play basic/concurrency.go /^func deepThought/,/END OMIT$/ HLdl * Go language runtime and tools - Go has a _runtime_ deadlock [[http://github.com/golang/go/blob/d2c81ad84776edfb4c790666f1d80554b4393d46/src/runtime/proc.go#L3243][detector]] - Go has a _runtime_ race detector - Guidelines/concurrency patterns exists to prevent deadlocks .code snippet/proc.txt * Yes, but... - *Runtime* detector (if not in execution path, no error) - Non-global deadlocks ignored by runtime detector .play deadlock/deadlock.go /^func main/,/}$/ - Developer experience, best practices (think `malloc` & `free` in C and `valgrind`) * Static analysis of Go source code .image talk-jan-2016/workflow.png - Research: π-calculus (process calculi like CSP) and Session Types - Try to find _potential_ deadlocks *1.* Convert to SSA Intermediate Representation (golang.org/x/tools/go/ssa) *2.* Extract communication (send/recv/select/close) as local types, ignore computation *3.* Model as Communicating FSM (state machine) * SSA IR SSA = Single Static Assignment In short: variables are assigned once (updates = new version of variable) func main(): 0: entry P:0 S:0 t0 = make chan int 0:int chan int t1 = make chan int 0:int chan int t2 = changetype chan<- int <- chan int (t0) chan<- int go Send(t2) t3 = changetype <-chan int <- chan int (t0) <-chan int t4 = changetype chan<- int <- chan int (t1) chan<- int go Recv(t3, t4) t5 = changetype <-chan int <- chan int (t0) <-chan int t6 = changetype chan<- int <- chan int (t1) chan<- int go Recv(t5, t6) go Work() t7 = <-t1 int t8 = <-t1 int return * Static Analysis - Track channel when they are created make(chan T) - Any send/receive on channel recorded as events (similar to Go oracle ChannelPeer) - But instead we build a graph of (all possible) events - we call it local types .image talk-jan-2016/extracted.png * Communicating Finite State Machine Aim: Global (Session) Graph Synthesis - i.e. all send ⇆ all recv - Satisfy relaxed (synchronous) Generalised Multiparty Compatibility conditions (Lange and Yoshida, POPL'15) .image talk-jan-2016/cfsm.png .caption Left: Communicating Finite State Machines; Right: Global graph - deadlocked vs. fixed (consistent) * // TODOs Tons of stuff missing but works on most simple programs - e.g. `sync` package, (Particularly `sync.WaitGroup`, i.e. barrier synchronisation) - e.g. buffered channels .link https://github.com/nickng/dingo-hunter .image talk-jan-2016/dingo-hunter.jpg ================================================ FILE: talks/templates/action.tmpl ================================================ {/* This is the action template. It determines how the formatting actions are rendered. */} {{define "section"}} {{.FormattedNumber}} {{.Title}} {{range .Elem}}{{elem $.Template .}}{{end}} {{end}} {{define "list"}}
    {{range .Bullet}}
  • {{style .}}
  • {{end}}
{{end}} {{define "text"}} {{if .Pre}}
{{range .Lines}}{{.}}{{end}}
{{else}}

{{range $i, $l := .Lines}}{{if $i}}{{template "newline"}} {{end}}{{style $l}}{{end}}

{{end}} {{end}} {{define "code"}}
{{.Text}}
{{end}} {{define "image"}}
{{end}} {{define "video"}}
{{end}} {{define "background"}}
{{end}} {{define "iframe"}} {{end}} {{define "link"}}
{{end}} {{define "html"}}{{.HTML}}{{end}} {{define "caption"}}
{{style .Text}}
{{end}} ================================================ FILE: talks/templates/article.tmpl ================================================ {/* This is the article template. It defines how articles are formatted. */} {{define "root"}} {{.Title}}
{{.Title}} {{with .Subtitle}}{{.}}{{end}}
{{with .Sections}}
{{template "TOC" .}}
{{end}} {{range .Sections}} {{elem $.Template .}} {{end}}{{/* of Section block */}} {{if .Authors}}

Authors

{{range .Authors}}
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}} {{end}}
{{if .PlayEnabled}} {{end}} {{end}} {{define "TOC"}}
    {{range .}}
  • {{.Title}}
  • {{with .Sections}}{{template "TOC" .}}{{end}} {{end}}
{{end}} {{define "newline"}} {{/* No automatic line break. Paragraphs are free-form. */}} {{end}} ================================================ FILE: talks/templates/dir.tmpl ================================================ Talks - The Go Programming Language

Go talks

{{with .Path}}

{{.}}

{{end}} {{with .Articles}}

Articles:

{{range .}}
{{.Name}}: {{.Title}}
{{end}}
{{end}} {{with .Slides}}

Slide decks:

{{range .}}
{{.Name}}: {{.Title}}
{{end}}
{{end}} {{with .Other}}

Files:

{{range .}}
{{.Name}}
{{end}}
{{end}} {{with .Dirs}}

Sub-directories:

{{range .}}
{{.Name}}
{{end}}
{{end}}
================================================ FILE: talks/templates/slides.tmpl ================================================ {/* This is the slide template. It defines how presentations are formatted. */} {{define "root"}} {{.Title}}

{{.Title}}

{{with .Subtitle}}

{{.}}

{{end}} {{if not .Time.IsZero}}

{{.Time.Format "2 January 2006"}}

{{end}} {{range .Authors}}
{{range .TextElem}}{{elem $.Template .}}{{end}}
{{end}}
{{range $i, $s := .Sections}}
{{if $s.Elem}}

{{$s.Title}}

{{range $s.Elem}}{{elem $.Template .}}{{end}} {{else}}

{{$s.Title}}

{{end}}
{{index $s.Number 0}}
{{end}}{{/* of Slide block */}}

Thank you

{{range .Authors}}
{{range .Elem}}{{elem $.Template .}}{{end}}
{{end}}
Use the left and right arrow keys or click the left and right edges of the page to navigate between slides.
(Press 'H' or navigate to hide this message.)
{{if .PlayEnabled}} {{end}} {{end}} {{define "newline"}}
{{end}} ================================================ FILE: templates/index.tmpl ================================================ {{.Title}}
{{- if .Examples -}} {{- end -}}
No output.
================================================ FILE: webservice/cfsm.go ================================================ package webservice import ( "bytes" "encoding/json" "io/ioutil" "log" "net/http" "github.com/nickng/dingo-hunter/cfsmextract" "github.com/nickng/dingo-hunter/cfsmextract/sesstype" "github.com/nickng/dingo-hunter/ssabuilder" ) func cfsmHandler(w http.ResponseWriter, req *http.Request) { b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input Go source code").Report(w) } req.Body.Close() conf, err := ssabuilder.NewConfigFromString(string(b)) if err != nil { NewErrInternal(err, "Cannot initialise SSA").Report(w) } ssainfo, err := conf.Build() if err != nil { NewErrInternal(err, "Cannot build SSA").Report(w) } extract := cfsmextract.New(ssainfo, "extract", "/tmp") go extract.Run() select { case <-extract.Error: NewErrInternal(err, "CFSM extraction failed").Report(w) case <-extract.Done: log.Println("CFSMs: analysis completed in", extract.Time) } cfsms := sesstype.NewCFSMs(extract.Session()) bufCfsm := new(bytes.Buffer) cfsms.WriteTo(bufCfsm) dot := sesstype.NewGraphvizDot(extract.Session()) bufDot := new(bytes.Buffer) dot.WriteTo(bufDot) reply := struct { CFSM string `json:"CFSM"` Dot string `json:"dot"` Time string `json:"time"` }{ CFSM: bufCfsm.String(), Dot: bufDot.String(), Time: extract.Time.String(), } json.NewEncoder(w).Encode(&reply) } ================================================ FILE: webservice/errors.go ================================================ package webservice import ( "fmt" "log" "net/http" ) type ErrInternal struct { cause error msg string } func NewErrInternal(cause error, message string) *ErrInternal { return &ErrInternal{cause: cause, msg: message} } func (e *ErrInternal) Error() string { return fmt.Sprintf("%s: %v", e.msg, e.cause) } // Report sends internal server error to web client also logs to console. func (e *ErrInternal) Report(w http.ResponseWriter) { http.Error(w, e.Error(), http.StatusInternalServerError) log.Fatal(e) } ================================================ FILE: webservice/gong.go ================================================ package webservice import ( "encoding/json" "io/ioutil" "log" "net/http" "os" "os/exec" "strings" "time" ) func gongHandler(w http.ResponseWriter, req *http.Request) { log.Println("Running Gong on snippet") b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input MiGo types").Report(w) } req.Body.Close() file, err := ioutil.TempFile(os.TempDir(), "gong") if err != nil { NewErrInternal(err, "Cannot create temp file for MiGo input").Report(w) } defer os.Remove(file.Name()) if _, err := file.Write(b); err != nil { NewErrInternal(err, "Cannot write to temp file for MiGo input").Report(w) } if err := file.Close(); err != nil { NewErrInternal(err, "Cannot close temp file for MiGo input").Report(w) } Gong, err := exec.LookPath("Gong") if err != nil { NewErrInternal(err, "Cannot find Gong executable (Check $PATH?)").Report(w) } startTime := time.Now() out, err := exec.Command(Gong, file.Name()).CombinedOutput() if err != nil { log.Printf("Gong execution failed: %v\n", err) } execTime := time.Now().Sub(startTime) replacer := strings.NewReplacer("", "", "", "", "", "") reply := struct { Gong string `json:"Gong"` Time string `json:"time"` }{ Gong: replacer.Replace(string(out)), Time: execTime.String(), } log.Println("Gong completed in", execTime.String()) json.NewEncoder(w).Encode(&reply) } ================================================ FILE: webservice/handlers.go ================================================ package webservice import ( "html/template" "io" "io/ioutil" "log" "net/http" "os" "path" ) var ( ExamplesDir string TemplateDir string StaticDir string ) func indexHandler(w http.ResponseWriter, req *http.Request) { var examples []string t, err := template.ParseFiles(path.Join(TemplateDir, "index.tmpl")) if err != nil { NewErrInternal(err, "Cannot load template").Report(w) } d, err := ioutil.ReadDir(ExamplesDir) if err != nil { NewErrInternal(err, "Cannot read examples").Report(w) } for _, f := range d { if f.IsDir() { examples = append(examples, f.Name()) } } data := struct { Title string Examples []string }{ Title: "GoInfer/Gong demo", Examples: examples, } err = t.Execute(w, data) if err != nil { NewErrInternal(err, "Template execute failed").Report(w) } } func loadHandler(w http.ResponseWriter, req *http.Request) { b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input").Report(w) } if err := req.Body.Close(); err != nil { NewErrInternal(err, "Cannot close request").Report(w) } log.Println("Load example:", string(b)) file, err := os.Open(path.Join(ExamplesDir, string(b), "main.go")) if err != nil { NewErrInternal(err, "Cannot open file").Report(w) } io.Copy(w, file) } ================================================ FILE: webservice/migo.go ================================================ package webservice import ( "encoding/json" "io/ioutil" "log" "net/http" "github.com/nickng/dingo-hunter/migoextract" "github.com/nickng/dingo-hunter/ssabuilder" "github.com/nickng/migo/v3/migoutil" ) func migoHandler(w http.ResponseWriter, req *http.Request) { b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input Go source code").Report(w) } req.Body.Close() conf, err := ssabuilder.NewConfigFromString(string(b)) if err != nil { NewErrInternal(err, "Cannot initialise SSA").Report(w) } info, err := conf.Build() if err != nil { NewErrInternal(err, "Cannot build SSA").Report(w) } extract, err := migoextract.New(info, ioutil.Discard) go extract.Run() select { case <-extract.Error: NewErrInternal(err, "MiGo type inference failed").Report(w) case <-extract.Done: log.Println("MiGo: analysis completed in", extract.Time) migoutil.SimplifyProgram(extract.Env.MigoProg) } reply := struct { MiGo string `json:"MiGo"` Time string `json:"time"` }{ MiGo: extract.Env.MigoProg.String(), Time: extract.Time.String(), } json.NewEncoder(w).Encode(&reply) } ================================================ FILE: webservice/play.go ================================================ package webservice import ( "bytes" "fmt" "go/build" "io/ioutil" "log" "net/http" "net/url" "path/filepath" "time" "golang.org/x/tools/godoc/static" "golang.org/x/tools/playground/socket" ) const basePkg = "golang.org/x/tools/cmd/present" var scripts = []string{"jquery.js", "jquery-ui.js", "playground.js", "play.js"} func initPlayground(origin *url.URL) { p, err := build.Default.Import(basePkg, "", build.FindOnly) if err != nil { log.Fatalf("Could not find gopresent files: %v", err) } basePath := p.Dir playScript(basePath, "SocketTransport") http.Handle("/socket", socket.NewHandler(origin)) } func playScript(root, transport string) { modTime := time.Now() var buf bytes.Buffer for _, p := range scripts { if s, ok := static.Files[p]; ok { buf.WriteString(s) continue } b, err := ioutil.ReadFile(filepath.Join(root, "static", p)) if err != nil { panic(err) } buf.Write(b) } fmt.Fprintf(&buf, "\ninitPlayground(new %v());\n", transport) b := buf.Bytes() http.HandleFunc("/play.js", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-type", "application/javascript") http.ServeContent(w, r, "", modTime, bytes.NewReader(b)) }) } ================================================ FILE: webservice/server.go ================================================ package webservice import ( "fmt" "log" "net" "net/http" "net/url" "sync" ) type Server struct { listener net.Listener iface string port string listenerMtx sync.Mutex } func NewServer(iface string, port string) *Server { return &Server{ iface: iface, port: port, } } func (s *Server) Start() { origin := &url.URL{Scheme: "http", Host: net.JoinHostPort(s.iface, s.port)} initPlayground(origin) http.HandleFunc("/", indexHandler) fs := http.FileServer(http.Dir(StaticDir)) http.Handle("/static/", http.StripPrefix("/static/", fs)) http.HandleFunc("/ssa", ssaHandler) http.HandleFunc("/load", loadHandler) http.HandleFunc("/cfsm", cfsmHandler) http.HandleFunc("/migo", migoHandler) http.HandleFunc("/gong", gongHandler) http.HandleFunc("/synthesis", synthesisHandler) log.Printf("Listening at %s", s.URL()) (&http.Server{}).Serve(s.Listener()) } func (s *Server) Close() { s.Listener().Close() } func (s *Server) URL() string { return fmt.Sprintf("http://%s/", s.Listener().Addr()) } func (s *Server) Listener() net.Listener { s.listenerMtx.Lock() defer s.listenerMtx.Unlock() if s.listener != nil { return s.listener } ifaceAndPort := fmt.Sprintf("%v:%v", s.iface, s.port) listener, err := net.Listen("tcp4", ifaceAndPort) if err != nil { log.Fatal(err) } s.listener = listener return s.listener } ================================================ FILE: webservice/ssa.go ================================================ package webservice import ( "io/ioutil" "net/http" "github.com/nickng/dingo-hunter/ssabuilder" ) func ssaHandler(w http.ResponseWriter, req *http.Request) { b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input Go source code").Report(w) } req.Body.Close() conf, err := ssabuilder.NewConfigFromString(string(b)) if err != nil { NewErrInternal(err, "Cannot initialise SSA").Report(w) } info, err := conf.Build() if err != nil { NewErrInternal(err, "Cannot build SSA").Report(w) } info.WriteTo(w) } ================================================ FILE: webservice/synthesis.go ================================================ package webservice import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" "os/exec" "path" "strings" "time" ) func synthesisHandler(w http.ResponseWriter, req *http.Request) { log.Println("Running SMC check on snippet") b, err := ioutil.ReadAll(req.Body) if err != nil { NewErrInternal(err, "Cannot read input CFSM").Report(w) } req.Body.Close() chanCFSMs := req.FormValue("chan") // ---- Executables ---- log.Println("Finding required executables") gmc, err := exec.LookPath("GMC") if err != nil { NewErrInternal(err, "Cannot find GMC executable (Check $PATH?)").Report(w) } bg, err := exec.LookPath("BuildGlobal") if err != nil { NewErrInternal(err, "Cannot find BuildGobal executable (Check $PATH?)").Report(w) } petrify, err := exec.LookPath("petrify") if err != nil { NewErrInternal(err, "Cannot find petrify executable (Check $PATH?)").Report(w) } dot, err := exec.LookPath("dot") if err != nil { NewErrInternal(err, "Cannot find dot executable (Check $PATH?)").Report(w) } // ---- Output dirs/files ---- baseDir := path.Join(os.TempDir(), "syn") err = os.MkdirAll(baseDir, 0777) if err != nil { NewErrInternal(err, "Cannot create temp dir").Report(w) } err = os.MkdirAll(path.Join(baseDir, "outputs"), 0777) if err != nil { NewErrInternal(err, "Cannot create final output dir").Report(w) } err = os.Chdir(baseDir) if err != nil { NewErrInternal(err, "Cannot chdir to temp dir").Report(w) } file, err := ioutil.TempFile(baseDir, "cfsm") if err != nil { NewErrInternal(err, "Cannot create temp file for CFSM input").Report(w) } defer os.Remove(file.Name()) toPetrifyPath := path.Join(baseDir, "outputs", fmt.Sprintf("%s_toPetrify", path.Base(file.Name()))) petriPath := path.Join(baseDir, "default") machinesDotPath := path.Join(baseDir, "outputs", fmt.Sprintf("%s_machines.dot", path.Base(file.Name()))) globalDotPath := path.Join(baseDir, "outputs", "default_global.dot") if _, err := file.Write(b); err != nil { NewErrInternal(err, "Cannot write to temp file for CFSM input").Report(w) } if err := file.Close(); err != nil { NewErrInternal(err, "Cannot close temp file for CFSM input").Report(w) } // Replace symbols re := strings.NewReplacer("AAA", "->", "CCC", ",", "COCO", ":") outReplacer := strings.NewReplacer("True", "True", "False", "False") startTime := time.Now() gmcOut, err := exec.Command(gmc, file.Name(), chanCFSMs, "+RTS", "-N").CombinedOutput() if err != nil { log.Printf("GMC execution failed: %v\n", err) } petriOut, err := exec.Command(petrify, "-dead", "-ip", toPetrifyPath).CombinedOutput() if err != nil { log.Printf("petrify execution failed: %v\n", err) } ioutil.WriteFile(petriPath, []byte(re.Replace(string(petriOut))), 0664) bgOut, err := exec.Command(bg, petriPath).CombinedOutput() if err != nil { log.Printf("BuildGlobal execution failed: %v\n", err) } log.Println("BuildGlobal:", string(bgOut)) execTime := time.Now().Sub(startTime) machinesSVG, err := exec.Command(dot, "-Tsvg", machinesDotPath).CombinedOutput() if err != nil { log.Printf("dot execution failed for : %v\n", err) } globalSVG, err := exec.Command(dot, "-Tsvg", globalDotPath).CombinedOutput() if err != nil { log.Printf("dot execution failed for : %v\n", err) } reply := struct { SMC string `json:"SMC"` Machines string `json:"Machines"` Global string `json:"Global"` Time string `json:"time"` }{ SMC: outReplacer.Replace(string(gmcOut)), Machines: string(machinesSVG), Global: string(globalSVG), Time: execTime.String(), } log.Println("Synthesis completed in", execTime.String()) json.NewEncoder(w).Encode(&reply) }