├── .gitignore ├── LICENSE ├── README.md ├── main.go ├── weave ├── advice.go ├── aspect.go ├── aspect_test.go ├── ast.go ├── build.go ├── build_darwin.go ├── build_linux.go ├── build_windows.go ├── declaration.go ├── declaration_test.go ├── file.go ├── get.go ├── get_test.go ├── go_routines.go ├── imports.go ├── pointcut.go ├── pointcut_test.go ├── replace.go ├── set.go ├── set_test.go ├── transform.go ├── weave.go ├── weave_test.go ├── within.go └── within_test.go └── wercker.yml /.gitignore: -------------------------------------------------------------------------------- 1 | example2/example2 2 | example3/example3 3 | example4/example4 4 | example/example 5 | goweave 6 | 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014- DeferPanic (https://deferpanic.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | goweave 2 | Aspect Oriented Programming for Go 3 | 4 | [![wercker status](https://app.wercker.com/status/7d7912cec649bc9763736051f64da3fa/s/master "wercker status")](https://app.wercker.com/project/bykey/7d7912cec649bc9763736051f64da3fa) 5 | 6 | [![GoDoc](https://godoc.org/github.com/deferpanic/goweave?status.svg)](https://godoc.org/github.com/deferpanic/goweave) 7 | 8 | 9 | ![Weave](http://i.imgur.com/JUUgIuv.png) 10 | 11 | ### WARNING - Major Hackage 12 | 13 | This really isn't meant to be used by anyone yet - definitely not in a 14 | production environment - you have been warned! 15 | 16 | Many 'design decisions' were not decisions at all - they were simply 17 | the "most simplest thing that could work". Lots of work left to do. 18 | 19 | ### TOC 20 | 21 | [What is AOP](https://github.com/deferpanic/goweave#what_is_aop) 22 | 23 | [Why](https://github.com/deferpanic/goweave#why) 24 | 25 | [Usage](https://github.com/deferpanic/goweave#usage) 26 | 27 | [Examples](https://github.com/deferpanic/loom#examples) 28 | 29 | [Loom](https://github.com/deferpanic/loom) 30 | 31 | [FAQ](https://github.com/deferpanic/goweave#faq) 32 | 33 | [Info](https://github.com/deferpanic/goweave#info) 34 | 35 | [Reserved Keywords](https://github.com/deferpanic/goweave#reserved-keywords) 36 | 37 | [Differences](https://github.com/deferpanic/goweave#differences) 38 | 39 | [Performance](https://github.com/deferpanic/goweave#performance) 40 | 41 | [Tests](https://github.com/deferpanic/goweave#tests) 42 | 43 | [Help](https://github.com/deferpanic/goweave#help) 44 | 45 | [Todo](https://github.com/deferpanic/goweave#todo) 46 | 47 | [Roadmap](https://github.com/deferpanic/goweave#roadmap) 48 | 49 | ## What is AOP !?? 50 | 51 | [Aspect oriented programming](http://docs.jboss.org/aop/1.1/aspect-framework/userguide/en/html/what.html) 52 | 53 | In short - this is a pre-processor that generates code defined by a 54 | goweave specification file - it's not a proper grammar yet although it 55 | probably should be. 56 | 57 | ### Why!??! 58 | 59 | > "... which is our fulltime job, write a program to write a program" 60 | > rob pike 61 | 62 | The critics yell red in the face - "We came to go to get away from enterprise java!! 63 | What the hell is the matter with you!?" 64 | 65 | I agree this concept can and has been abused in the past. 66 | 67 | However, I'm definitely not a code purist - to me coding is a tool first and 68 | foremost. 69 | 70 | I simply wanted an easy way to attach common bits of code to large 71 | existing codebases without having to hack it in each time to each 72 | different codebase. I also needed the ability to do this on large 73 | projects without modifying the source. 74 | 75 | Sure I could write method wrappers, I could change code, etc. but I 76 | don't want to be constantly writing/re-writing/re-moving/inserting tons 77 | of code just to check things out. 78 | 79 | Automate all the things. For me, this is simply a powertool for deep introspection of go programs. 80 | 81 | That is the rationale behind this. 82 | 83 | ### Existing Tools: 84 | 85 | #### [go fmt](https://golang.org/pkg/fmt/) 86 | This is actually used for around advice currently. It allows you to wrap 87 | methods. Having said that - we wish to do more proper around advice than 88 | simply re-writing the function declaration. 89 | 90 | ex: 91 | 92 | ```go 93 | gofmt -r 'bytes.Compare(a, b) == 0 -> bytes.Equal(a, b)' 94 | ``` 95 | 96 | #### [go fix](https://golang.org/cmd/fix/): 97 | This is one hell of an awesome tool. I just think it's a little 98 | too low-level for what we are wanting to do. Remember - one of the 99 | solutions of this tool is to make things as trivial as possible to 100 | insert new functionality. 101 | 102 | #### [go cover](https://godoc.org/golang.org/x/tools/cmd/cover): 103 | This is used to provide code coverage and has similar properties 104 | to what we want. 105 | 106 | #### [go generate](http://blog.golang.org/generate): 107 | We are generating code but we are looking for more extensive code 108 | generation. 109 | 110 | ### Usage: 111 | 112 | Where you might use 113 | 114 | ```go 115 | go build 116 | ``` 117 | 118 | simply replace with 119 | 120 | ```go 121 | goweave 122 | ``` 123 | 124 | Note: depending on how you build your go this may or may not work - 125 | patches/pulls to make this more generic for everyone are definitely 126 | welcome. 127 | 128 | ### Use Cases 129 | * error detection && correction 130 | (ex: force logging of errors on any methods with this declaration) 131 | 132 | * data validation 133 | (ex: notate that this data was invalid but allow it to continue) 134 | 135 | * i18n 136 | (ex: translate this to esperanto if you can't detect the language) 137 | 138 | * security 139 | (ex: authenticate this user in each http request) 140 | 141 | * caching 142 | (ex: cache these variables when exposed in a http request) 143 | 144 | * logging 145 | (ex: log when this group of users accesses these methods) 146 | 147 | * monitoring 148 | (ex: ensure that if this channel closes we always alert joebob) 149 | 150 | * metrics 151 | (ex: cnt the number of times this function is called) 152 | 153 | * tracing 154 | (ex: print out the value of this variable in a pkg) 155 | 156 | * dealing with legacy code 157 | (ex: overriding a method/API w/minimum of work) 158 | 159 | * static validation 160 | (ex: force closing a file if we detect that it hasn't been closed) 161 | 162 | ### Grammar: 163 | 164 | The aspect 'grammar' if you can call it that is a total piece right 165 | now. It is a little bit of go, a little of json, etc. It is most definitely 166 | not going to stay the same - it will be improved in the future. 167 | 168 | I apologize for giving you the forks to stab your collective eyes out. 169 | 170 | I think a good goal to have is to make it as proper go as possible and 171 | then extend it maybe through comments. 172 | 173 | Suggestions/pull requests/discussions more than welcome! 174 | 175 | ### Definitions: 176 | 177 | I probably have not defined certain things properly here - open a pull 178 | request if you find something off. 179 | 180 | ### Join Points 181 | 182 | Places in your code you can apply behavior. 183 | 184 | ### Aspects: 185 | 186 | A .weave file that contains our behavior. 187 | 188 | Right now we support multiple .weave projects for a project and they 189 | will apply advice recursively over a project. 190 | 191 | The programming theory department says that aspects are common features 192 | that you use everywhere that don't really have anything at all to do with 193 | your domain logic. Logging is a canonical example - most everything you 194 | log does not really have anything to do with all the other stuff you 195 | log. 196 | 197 | Similarly if you had a http controller that whenever you got a request 198 | you would update a metric counter for that controller but you do this on 199 | each api controller - that really has nothing at all to do with the 200 | controller logic itself. The metric might simply be another aspect that 201 | is common everywhere. 202 | 203 | Once again someone might point out why don't you just make a method 204 | and then wrap each call? The point here is that 1) we don't want to 205 | modify code, 2) we might not *know* all the places that happens and 206 | could easily leave something out, 3) we are eternally lazy and would 207 | rather the computer do this for us. 208 | 209 | ### PointCut: 210 | 211 | An expression that details where to apply behavior. 212 | 213 | Pointcuts in other languages such as java can commonly use annotations 214 | -- we currently don't support this as we want to be un-obtrusive as possible 215 | -- that is - we don't want to modify go source 216 | 217 | We support {call, execute, within} pointcut primitives right now: 218 | Also we are hacking in local/global get/sets and declarations. 219 | 220 | __call__: 221 | 222 | These happen before, after or wrap around calling a method. The code is outside of the function. 223 | 224 | __execute__: 225 | 226 | These happen before or after executing a method. The code is put inside the method. 227 | 228 | __within__: 229 | 230 | These happen for *every* statement within a function body declaration. 231 | 232 | ---------- 233 | 234 | __get__: 235 | 236 | These fire when a local/global variable has a get operation. 237 | 238 | __set__: 239 | 240 | These fire when a local/global variable has a set operation. 241 | 242 | __declaration__: 243 | 244 | This fires when a variable is declared. 245 | 246 | 247 | All pointcuts are currently defined only on functions. Struct field 248 | members are definitely a future feature we could support although go 249 | generate might do this acceptably already. 250 | 251 | Note: this 'grammar' if you can call it is nowhere close to 'ok' - expect it to 252 | change "heavily". 253 | 254 | #### explicit method name 255 | ```go 256 | call(blah()) 257 | ``` 258 | 259 | ```go 260 | execute(blah()) 261 | ``` 262 | 263 | #### partial match method name - TODO 264 | 265 | ```go 266 | call(b.*) 267 | ``` 268 | 269 | ```go 270 | execute(b.*) 271 | ``` 272 | 273 | #### function declaration w/wildcard arguments 274 | ```go 275 | call(http.HandleFunc(d, s)) 276 | ``` 277 | 278 | #### wildcard function name w/explicit arguments 279 | execute((w http.ResponseWriter, r *http.Request)) 280 | 281 | #### doesn't work yet - TODO 282 | 283 | sub-pkg && method name 284 | ```go 285 | execute(pkg/blah()) 286 | ``` 287 | 288 | sub-pkg && struct && method-name 289 | ```go 290 | execute(pkg/struct.b()) 291 | ``` 292 | 293 | #### struct && method name - TODO 294 | ```go 295 | execute(struct.b()) 296 | ``` 297 | 298 | ### Advice: 299 | 300 | Behavior to apply: 301 | 302 | * before 303 | * after 304 | * around 305 | 306 | Around advice currently only works with call pointcuts. 307 | 308 | We currently support the following advice: 309 | 310 | #### call examples: 311 | 312 | ```go 313 | some.stuff() 314 | ``` 315 | 316 | Code will be executed {before, around, after} this call. 317 | 318 | __call before:__ 319 | ```go 320 | fmt.Println("before") 321 | some.stuff() 322 | ``` 323 | 324 | __call after:__ 325 | ```go 326 | some.stuff() 327 | fmt.Println("before") 328 | ``` 329 | 330 | __call around:__ 331 | ``` 332 | somewrapper(some.stuff()) 333 | ``` 334 | 335 | #### execute examples: 336 | 337 | ```go 338 | func stuff() { 339 | fmt.Println("stuff") 340 | } 341 | ``` 342 | 343 | __execute before:__ 344 | ```go 345 | func stuff() { 346 | fmt.Println("before") 347 | fmt.Println("stuff") 348 | } 349 | ``` 350 | 351 | __execute after:__ 352 | ```go 353 | func stuff() { 354 | fmt.Println("stuff") 355 | fmt.Println("after") 356 | } 357 | ``` 358 | 359 | #### within examples: 360 | 361 | ```go 362 | func blah() { 363 | slowCall() 364 | fastCall() 365 | } 366 | ``` 367 | 368 | __within before:__ 369 | ```go 370 | func blah() { 371 | beforeEach() 372 | slowCall() 373 | beforeEach() 374 | fastcall() 375 | } 376 | ``` 377 | 378 | ### Goals 379 | 380 | * FAST - obviously there will always be greater overhead than just 381 | running go build but we don't want this to be obscene - right now it's 382 | a little obscene 383 | 384 | * CORRECT - it goes w/out saying this is highly important to be as 385 | correct as possible w/our code generation 386 | 387 | * NO CODE MODIFICATIONS - my main use cases involve *not* modifying code 388 | so that is why we initially did not support annotations - I'm not 389 | opposed to adding these but that's not my intended goal 390 | 391 | * create tooling around AO development for go 392 | 393 | * maybe move towards compiler extension? 394 | 395 | ### FAQ 396 | 397 | #### Why not do everything via the AST? 398 | We are moving all the regexen to AST modifications. This started out as a 399 | POC and I wanted functionality first - correctness comes after. 400 | 401 | #### Why not modify the AST instead of re-writing the source each time an 402 | aspect is applied? 403 | We also want to do this - once again - it was a POC and 404 | correctness/speed comes later. Pull requests welcome. 405 | 406 | #### What about IR generation? 407 | 408 | #### What about aspects on binary/closed-source? 409 | This is arguably one of the bigger benefits of AOP (at least for our 410 | purposes) and it's definitely something we intend to support/code for in 411 | the future. 412 | 413 | That's a long ways away but not off the radar/roadmap. 414 | 415 | #### Why wouldn't you just code this into your source? 416 | A couple of reasons. 417 | 418 | 1) If you are going to do something like development tracing (eg: 419 | sprinkle some fmt.Println everywhere) you don't want that in your 420 | production code. It's much better to apply it when necessary in your 421 | binary, fix the problem and go - there is no need to code it in, then 422 | hack it back out (and potentially miss some). It's *much* cleaner this 423 | way and it's *much* faster. 424 | 425 | 2) The original reason we did this was over at DeferPanic we had many 426 | requests from people wanting to use our code to automatically insert 427 | code in. For existing codebases this was a lot of work. After we made a 428 | [tool](https://github.com/deferpanic/deprehend) that did this code 429 | generation we had requests to make it non-obtrusive - that is - they 430 | didn't want the code inside their codebase - just available to them at 431 | runtime. 432 | 433 | 3) I'd like the ability to turn on/off the behavior at will *and* not 434 | have to re-code it for every project. I think this is where this really 435 | shines. 436 | 437 | #### Are you all insane? This is go heresey!! Burn them at the stake! 438 | 439 | :) We are practioners of the "get-shit-done" philosophy. Whether this 440 | is considered good or bad practice is not quite a concern for our 441 | usecases. 442 | 443 | We only care about - how fast can I get this done? 444 | 445 | Our use cases usually entail us having to jump into brand new large 446 | codebases and we want to send 'tracer bullets' out very very fast. This 447 | style of programming allows us to do that. 448 | 449 | ### Info 450 | 451 | * builds are currently made in ~/go/src/_weave - I don't think this is 452 | ideal so am open to further suggestions 453 | 454 | * builds are *really* *really* slow right now - there is a lot of file 455 | I/O we shouldn't be doing - many files we read/write multiple times - 456 | this goes hand in hand w/the text processing - most of this should just 457 | be moved to the AST processing - plenty of cruft laying around as well 458 | that needs to be refactored 459 | 460 | ### Reserved Keywords 461 | 462 | Right now the only time you'll run into reserved keywords are in the 463 | experimental 'within' and 'get'/'set' advice section although there is an intention to 464 | support a set of keywords that one can use in their aspects. 465 | 466 | * mName 467 | If you use 'mName' within your within advice it will translate to a 468 | string representation of the joinpoint found by your within pointcut. 469 | 470 | * mAvars 471 | this is a list of abstract variables used within get/set advice 472 | 473 | We'd appreciate help from the community formulating a more formal 474 | approach for this. Namespacing, the set of keywords supported, etc. 475 | 476 | ### Differences 477 | 478 | There are many differences between this and other AOP implementations 479 | such as AspectJ. 480 | 481 | Firstly, since go has no vm executing bytecode so we don't weave at the bytecode 482 | level. Currently we weave at source code level at build time. 483 | 484 | Secondly, we don't currently support annotations. I'm open to this in 485 | the future but it wasn't a primary usecase for me so I didn't add them. 486 | 487 | ### Performance 488 | 489 | Is pretty bad right now. Lots of needless reading/writing of files. Part of 490 | this was cause it started out with regex/line parsing and slowly moved 491 | towards AST manipulating. 492 | 493 | Once most of the file rewriting is moved towards AST modification the 494 | performance should imrpove dramatically but right now it really sucks 495 | and is going to make you cry. 496 | 497 | Lots of further work in this area to do. If you want to help - pulls 498 | are definitely welcome. 499 | 500 | For a point of reference - on a well known web application it takes 12 501 | seconds to build versus 1.59 seconds with just go build. 502 | 503 | We'd very much like help from the community in refactoring some of 504 | these performance problems. 505 | 506 | ### Tests 507 | 508 | The tests are very brittle right now and are more functional than unit 509 | based. Lots of work here to do. Once most of the file reading/writing 510 | stuff is replaced with AST replacement transformations the tests should be much more specific not to mention must faster. 511 | 512 | I really don't like the fact that the tests are the way they are right 513 | now but just need to ensure certain things work until we refactor it. 514 | 515 | We would appreciate any help from the community in refactoring the 516 | code so the tests aren't big blocks of text - no need for that. 517 | 518 | ### What You Should Know Before Using 519 | 520 | This is *alpha* software - at best. It's more of an idea right now than anything 521 | else. 522 | 523 | * Expect the "grammars" {aspects, pointcuts} to change. 524 | 525 | * This is currently *much* slower compared to native go build. Expect that to 526 | change but right now it's slow. 527 | 528 | * Expect the build system to change soon. It's slow and crap. Getting 529 | the latency down is very much an immediate goal. 530 | 531 | * This *might* eat your cat - watch out. 532 | 533 | ### TODO 534 | #### a.k.a. - Known Suckiness 535 | 536 | * add ability to add global function advice to pkg 537 | 538 | * make everything use ast - no raw txt 539 | 540 | * multi-pass parsing 541 | - this should ideally be a single pass 542 | - most of the regex/line scanning should be converted to AST node 543 | replacement 544 | 545 | * add support for matching function declaration w/returns 546 | 547 | * import vendoring/re-writing 548 | -- very open to different ways of approaching this - it kinda sucks 549 | right now 550 | 551 | * better error handling 552 | - can do bail outs if parser doesn't emit correctly 553 | 554 | * matching function declarations 555 | - with arguments 556 | - with return arguments 557 | - partial function matching 558 | 559 | * scope 560 | - for the regex && line-editing stuff this is completely naive - pulls 561 | pls 562 | 563 | * annotations?? 564 | 565 | * Faster 566 | 567 | * Better Test coverage 568 | 569 | ### Pointcut Todo 570 | * create a more proper language 571 | 572 | * match against method receivers 573 | * match against return arguments 574 | * match stdlib 575 | * match 3rd pkgs 576 | 577 | ### Aspect Todo 578 | 579 | * ability to add functions to global namespace 580 | -- maybe just need some tests here 581 | 582 | ### Help 583 | 584 | Want to help? Ideas for helping out: 585 | 586 | * test coverage 587 | 588 | * benchmark coverage 589 | 590 | * documentation 591 | 592 | * sample aspects - aspects [should be shared on the loom](https://github.com/deferpanic/loom) 593 | - no need to re-invent the wheel 594 | 595 | Need helping visualizing what you are looking at? Check out http://goast.yuroyoro.net/ 596 | 597 | ### Roadmap 598 | 599 | #### Grammars 600 | * better aspect grammar 601 | * better pointcut grammar 602 | 603 | #### Parsing/Speed 604 | * move from regexen to AST 605 | * move from AST to IR 606 | 607 | #### Extending 608 | * add support for 3rd party pkgs 609 | * add support for stdlib 610 | 611 | #### Interface Pointcuts 612 | * be able to define on interface fields 613 | * be able to define on methods that satisfy interfaces 614 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/deferpanic/goweave/weave" 5 | "log" 6 | ) 7 | 8 | const ( 9 | version = "v0.1" 10 | ) 11 | 12 | // main is the main point of entry for running goweave 13 | func main() { 14 | log.Println("goweave " + version) 15 | 16 | w := weave.NewWeave() 17 | w.Run() 18 | 19 | } 20 | -------------------------------------------------------------------------------- /weave/advice.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Advice has a function to wrap advice around and code for said 8 | // function 9 | type Advice struct { 10 | before string 11 | after string 12 | around string 13 | 14 | adviceTypeId int 15 | } 16 | 17 | // adviceType returns a map of id to human expression of advice types 18 | func adviceType() map[int]string { 19 | return map[int]string{ 20 | 1: "before", 21 | 2: "after", 22 | 3: "around", 23 | } 24 | } 25 | 26 | // adviceKind returns the map id of human expression of advice type 27 | func adviceKind(l string) int { 28 | stuff := strings.Split(l, ": ") 29 | ostuff := strings.Split(stuff[1], " ") 30 | 31 | switch ostuff[0] { 32 | case "before": 33 | return 1 34 | case "after": 35 | return 2 36 | case "around": 37 | return 3 38 | } 39 | 40 | return -1 41 | } 42 | -------------------------------------------------------------------------------- /weave/aspect.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "strings" 7 | ) 8 | 9 | // aspect contains advice, pointcuts and any imports needed 10 | type Aspect struct { 11 | advize Advice 12 | pointkut Pointcut 13 | importz []string 14 | } 15 | 16 | // grab_aspects looks for an aspect file for each file 17 | // this seems lame and contrary to what we want... 18 | // I'd go as far as to say that we want this to be cross-pkg w/in root? 19 | // 20 | // maybe the rule should be - aspects are valid for anything in a 21 | // project root? 22 | func (w *Weave) loadAspects() { 23 | 24 | fz := w.findAspects() 25 | for i := 0; i < len(fz); i++ { 26 | 27 | buf, err := ioutil.ReadFile(fz[i]) 28 | if err != nil { 29 | w.flog.Println(err) 30 | } 31 | s := string(buf) 32 | 33 | w.parseAspectFile(s) 34 | } 35 | 36 | if len(w.aspects) == 0 { 37 | w.flog.Println("no weaves") 38 | os.Exit(1) 39 | } 40 | 41 | } 42 | 43 | // parseImports returns an array of imports for the corresponding advice 44 | func (w *Weave) parseImports(body string) []string { 45 | impbrace := strings.Split(body, "imports (") 46 | 47 | if len(impbrace) > 1 { 48 | end := strings.Split(impbrace[1], ")")[0] 49 | t := strings.TrimSpace(end) 50 | return strings.Split(t, "\n") 51 | } else { 52 | return []string{} 53 | } 54 | } 55 | 56 | // containsBefore returns true if the body has before advice 57 | func (w *Weave) containsBefore(body string) bool { 58 | if strings.Contains(body, "before: {") { 59 | return true 60 | } else { 61 | return false 62 | } 63 | } 64 | 65 | // containsAfter returns true if the body has after advice 66 | func (w *Weave) containsAfter(body string) bool { 67 | if strings.Contains(body, "after: {") { 68 | return true 69 | } else { 70 | return false 71 | } 72 | } 73 | 74 | // rightBraceCnt returns the number of right braces in a string 75 | func (w *Weave) rightBraceCnt(body string) int { 76 | return strings.Count(body, "}") 77 | } 78 | 79 | // parseAdvice returns advice about this aspect 80 | func (w *Weave) parseAdvice(body string) Advice { 81 | advize := strings.Split(body, "advice:")[1] 82 | 83 | a4t := "" 84 | b4t := "" 85 | ar4t := "" 86 | 87 | bbrace := strings.Split(advize, "before: {") 88 | if len(bbrace) > 1 { 89 | // fixme 90 | if w.containsAfter(bbrace[1]) { 91 | b4 := strings.Split(bbrace[1], "}")[0] 92 | b4t = strings.TrimSpace(b4) 93 | // ... 94 | } else { 95 | cnt := w.rightBraceCnt(bbrace[1]) 96 | // have at most 3 right braces 97 | // 3 - 3 = 0 98 | // 4 - 3 = 1 99 | b4 := strings.SplitAfter(bbrace[1], "}") 100 | rb := "" 101 | if cnt == 3 { 102 | rb = b4[0] 103 | rb = rb[:len(rb)-1] 104 | } else { 105 | for i := 0; i < cnt-3; i++ { 106 | rb += strings.TrimSpace(b4[i]) 107 | } 108 | } 109 | b4t = strings.TrimSpace(rb) 110 | } 111 | } 112 | 113 | abrace := strings.Split(advize, "after: {") 114 | if len(abrace) > 1 { 115 | a4 := strings.Split(abrace[1], "}")[0] 116 | a4t = strings.TrimSpace(a4) 117 | } 118 | 119 | arbrace := strings.Split(advize, "around: {") 120 | if len(arbrace) > 1 { 121 | ar4 := strings.Split(arbrace[1], "}")[0] 122 | ar4t = strings.TrimSpace(ar4) 123 | } 124 | 125 | return Advice{ 126 | before: b4t, 127 | after: a4t, 128 | around: ar4t, 129 | } 130 | 131 | } 132 | 133 | // parseAspectFile loads an individual file containing aspects 134 | // there be tigers here 135 | // subject to immediate and dramatic change 136 | func (w *Weave) parseAspectFile(body string) { 137 | results := []Aspect{} 138 | 139 | aspects := strings.Split(body, "aspect {") 140 | 141 | for i := 0; i < len(aspects); i++ { 142 | 143 | aspect := aspects[i] 144 | azpect := Aspect{} 145 | 146 | if strings.TrimSpace(aspect) != "" { 147 | 148 | pk, err := w.parsePointCut(aspect) 149 | if err != nil { 150 | w.flog.Println(err.Error() + aspect) 151 | os.Exit(1) 152 | } else { 153 | azpect.pointkut = pk 154 | } 155 | 156 | azpect.importz = w.parseImports(aspect) 157 | 158 | azpect.advize = w.parseAdvice(aspect) 159 | 160 | results = append(results, azpect) 161 | } 162 | 163 | } 164 | 165 | w.aspects = results 166 | 167 | } 168 | -------------------------------------------------------------------------------- /weave/aspect_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestBefore(t *testing.T) { 8 | 9 | f := ` 10 | aspect { 11 | pointcut: execute(main) 12 | advice: { 13 | before: { 14 | fmt.Println("before main") 15 | } 16 | } 17 | } 18 | ` 19 | 20 | w := &Weave{} 21 | w.parseAspectFile(f) 22 | 23 | if len(w.aspects) != 1 { 24 | t.Error("didn't parse aspects") 25 | } 26 | 27 | first := w.aspects[0] 28 | 29 | if first.pointkut.kind != 2 { 30 | t.Error("didn't set pointcut definition correctly") 31 | } 32 | 33 | if first.pointkut.def != "main" { 34 | t.Error("didn't set pointcut definition correctly") 35 | } 36 | 37 | if first.pointkut.funktion != "main" { 38 | t.Error("didn't set pointcut function correctly") 39 | } 40 | 41 | if len(first.importz) != 0 { 42 | t.Error("didn't parse imports correctly") 43 | } 44 | 45 | s := `fmt.Println("before main")` 46 | 47 | if first.advize.before != s { 48 | t.Error(s) 49 | t.Error(first.advize.before) 50 | t.Error("didn't parse advice correctly") 51 | } 52 | 53 | if first.advize.after != "" { 54 | t.Error("didn't parse advice correctly") 55 | } 56 | 57 | } 58 | 59 | func TestAfter(t *testing.T) { 60 | 61 | f := ` 62 | aspect { 63 | pointcut: execute(main) 64 | advice: { 65 | after: { 66 | fmt.Println("after main") 67 | } 68 | } 69 | } 70 | ` 71 | 72 | w := &Weave{} 73 | w.parseAspectFile(f) 74 | 75 | if len(w.aspects) != 1 { 76 | t.Error("didn't parse aspects") 77 | } 78 | 79 | first := w.aspects[0] 80 | 81 | if first.pointkut.def != "main" { 82 | t.Error("didn't set pointcut definition correctly") 83 | } 84 | 85 | if first.pointkut.funktion != "main" { 86 | t.Error("didn't set pointcut function correctly") 87 | } 88 | 89 | if len(first.importz) != 0 { 90 | t.Error("didn't parse imports correctly") 91 | } 92 | 93 | if first.advize.before != "" { 94 | t.Error("didn't parse advice correctly") 95 | } 96 | 97 | if first.advize.after != "fmt.Println(\"after main\")" { 98 | t.Error("didn't parse advice correctly") 99 | } 100 | 101 | } 102 | 103 | func TestParseAspectFile(t *testing.T) { 104 | 105 | f := ` 106 | aspect { 107 | pointcut: execute(main) 108 | imports ( 109 | "fmt" 110 | "github.com/some/pkg" 111 | ) 112 | advice: { 113 | before: { 114 | fmt.Println("before main") 115 | } 116 | after: { 117 | fmt.Println("after main") 118 | } 119 | } 120 | } 121 | ` 122 | 123 | w := &Weave{} 124 | w.parseAspectFile(f) 125 | 126 | if len(w.aspects) != 1 { 127 | t.Error("didn't parse aspects") 128 | } 129 | 130 | first := w.aspects[0] 131 | 132 | if first.pointkut.def != "main" { 133 | t.Error("didn't set pointcut definition correctly") 134 | } 135 | 136 | if first.pointkut.funktion != "main" { 137 | t.Error("didn't set pointcut function correctly") 138 | } 139 | 140 | if len(first.importz) != 2 { 141 | t.Error("didn't parse imports correctly") 142 | } 143 | 144 | if first.advize.before != "fmt.Println(\"before main\")" { 145 | t.Error("didn't parse advice correctly") 146 | } 147 | 148 | if first.advize.after != "fmt.Println(\"after main\")" { 149 | t.Error("didn't parse advice correctly") 150 | } 151 | 152 | } 153 | 154 | func TestNoImports(t *testing.T) { 155 | 156 | f := ` 157 | aspect { 158 | pointcut: execute(main) 159 | advice: { 160 | before: { 161 | fmt.Println("before main") 162 | } 163 | after: { 164 | fmt.Println("after main") 165 | } 166 | } 167 | } 168 | ` 169 | 170 | w := &Weave{} 171 | w.parseAspectFile(f) 172 | 173 | if len(w.aspects) != 1 { 174 | t.Error("didn't parse aspects") 175 | } 176 | 177 | first := w.aspects[0] 178 | 179 | if first.pointkut.def != "main" { 180 | t.Error("didn't set pointcut definition correctly") 181 | } 182 | 183 | if first.pointkut.funktion != "main" { 184 | t.Error("didn't set pointcut function correctly") 185 | } 186 | 187 | if len(first.importz) != 0 { 188 | t.Error("didn't parse imports correctly") 189 | } 190 | 191 | if first.advize.before != "fmt.Println(\"before main\")" { 192 | t.Error("didn't parse advice correctly") 193 | } 194 | 195 | if first.advize.after != "fmt.Println(\"after main\")" { 196 | t.Error("didn't parse advice correctly") 197 | } 198 | 199 | } 200 | 201 | func TestAspectScope(t *testing.T) { 202 | 203 | f := ` 204 | aspect { 205 | pointcut: execute(innerFors) 206 | advice: { 207 | before: { 208 | for i:=0; i<10; i++ { 209 | fmt.Println(i) 210 | } 211 | } 212 | } 213 | } 214 | ` 215 | 216 | w := &Weave{} 217 | w.parseAspectFile(f) 218 | 219 | if len(w.aspects) != 1 { 220 | t.Error("didn't parse aspects") 221 | } 222 | 223 | first := w.aspects[0] 224 | 225 | s := `for i:=0; i<10; i++ { 226 | fmt.Println(i) 227 | }` 228 | 229 | if first.advize.before != s { 230 | t.Error("didn't parse advice correctly") 231 | } 232 | 233 | } 234 | -------------------------------------------------------------------------------- /weave/ast.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "log" 7 | "strings" 8 | ) 9 | 10 | // stolen from http://golang.org/src/cmd/fix/fix.go 11 | func isPkgDot(expr ast.Expr, pkg, name string) bool { 12 | if len(pkg) > 0 { 13 | sel, ok := expr.(*ast.SelectorExpr) 14 | return ok && isIdent(sel.X, pkg) && isIdent(sel.Sel, name) 15 | } else { 16 | return isIdent(expr, name) 17 | } 18 | } 19 | 20 | // stolen from http://golang.org/src/cmd/fix/fix.go 21 | func isPtrPkgDot(t ast.Expr, pkg, name string) bool { 22 | ptr, ok := t.(*ast.StarExpr) 23 | return ok && isPkgDot(ptr.X, pkg, name) 24 | } 25 | 26 | // stolen from http://golang.org/src/cmd/fix/fix.go 27 | func isIdent(expr ast.Expr, ident string) bool { 28 | id, ok := expr.(*ast.Ident) 29 | return ok && id.Name == ident 30 | } 31 | 32 | // parseExpr returns an ast expression from the source s 33 | // stolen from http://golang.org/src/cmd/fix/fix.go 34 | func parseExpr(s string) ast.Expr { 35 | exp, err := parser.ParseExpr(s) 36 | if err != nil { 37 | log.Println("Cannot parse expression %s :%s", s, err.Error()) 38 | } 39 | return exp 40 | } 41 | 42 | // containArgs ensures the function signature is 'correct' 43 | // the following is left un-implemented and needs to be implemented 44 | // FIXME 45 | // 1) simple types - no pkgs 46 | // 2) order of arguments 47 | // 3) no args 48 | // 4) no simple args 49 | func containArgs(pk string, paramList []*ast.Field) bool { 50 | 51 | /* 52 | this function will return a channel through which 53 | we can get every argument's type of pfn 54 | */ 55 | nextarg := func(params []*ast.Field, stop chan int) <-chan ast.Expr { 56 | argTypeChnl := make(chan ast.Expr) 57 | go func() { 58 | defer close(argTypeChnl) 59 | for _, arg := range params { 60 | for range arg.Names { 61 | select { 62 | case argTypeChnl <- arg.Type: 63 | case <-stop: 64 | return 65 | } 66 | } 67 | } 68 | }() 69 | return argTypeChnl 70 | } 71 | 72 | stop := make(chan int) 73 | defer close(stop) 74 | 75 | argTypeChnl := nextarg(paramList, stop) 76 | 77 | //-------------------- 78 | 79 | pk = strings.Split(pk, "(")[1] 80 | pk = strings.Split(pk, ")")[0] 81 | 82 | arglist := strings.Split(pk, ",") 83 | 84 | if (len(arglist) == 1) && (arglist[0] == "") { 85 | arglist = []string{} 86 | } 87 | 88 | if len(arglist) == 0 && len(paramList) != 0 { 89 | return false 90 | } 91 | 92 | // Check whether every argument's type is the same 93 | for _, argtype := range arglist { 94 | 95 | argtype = strings.TrimSpace(argtype) 96 | typelist := strings.Split(argtype, ".") 97 | isptr := (argtype[:1] == "*") 98 | 99 | pkg := "" 100 | name := typelist[0] 101 | 102 | compareArgType := isPkgDot 103 | if isptr { 104 | compareArgType = isPtrPkgDot 105 | } 106 | 107 | // pkg.type or *pkg.type 108 | if len(typelist) == 2 { 109 | pkg = typelist[0] 110 | name = typelist[1] 111 | if isptr { 112 | pkg = pkg[1:] 113 | } 114 | } 115 | 116 | nextArgType, ok := <-argTypeChnl 117 | 118 | if !ok || !compareArgType(nextArgType, pkg, name) { 119 | return false 120 | } 121 | } 122 | 123 | return true 124 | } 125 | -------------------------------------------------------------------------------- /weave/build.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "os" 7 | "os/exec" 8 | "path/filepath" 9 | "strings" 10 | ) 11 | 12 | type osCmdInfo struct { 13 | shell []string // Shell命令 14 | mkdir string 15 | rmdir string 16 | pwd string 17 | cp string 18 | xcp string 19 | exeext string // 可执行文件扩展名 20 | envsep string // GOPATH环境变量中用来分隔不同路径的字符 21 | } 22 | 23 | var ( 24 | oscmdenv *osCmdInfo 25 | ) 26 | 27 | // buildDir determines what the root build dir is 28 | func (w *Weave) buildDir() string { 29 | return buildDir() 30 | } 31 | 32 | func execShellCmd(cmd string) (out []byte, err error) { 33 | arglist := append(oscmdenv.shell, cmd) 34 | out, err = exec.Command(arglist[0], arglist[1:]...).CombinedOutput() 35 | if err != nil { 36 | log.Printf("\ncmd: %s\nerr: %v\n", cmd, err) 37 | } 38 | return 39 | } 40 | 41 | // buildDir determines what the root build dir is 42 | func buildDir() string { 43 | out, _ := execShellCmd(oscmdenv.pwd) 44 | return strings.TrimSpace(string(out)) 45 | } 46 | 47 | // binName returns the expected bin name 48 | func (w *Weave) binName() string { 49 | s := w.buildDir() 50 | stuff := strings.Split(s, string(filepath.Separator)) 51 | return stuff[len(stuff)-1] + oscmdenv.exeext 52 | } 53 | 54 | // whichgo determines provides the full go path to the current go build 55 | // tool 56 | func (w *Weave) whichGo() string { 57 | return "go" 58 | } 59 | 60 | // tmpLocation returns the tmp build dir 61 | func (w *Weave) tmpLocation() string { 62 | return tmpLocation() 63 | } 64 | 65 | func tmpLocation() string { 66 | gopath := os.Getenv("GOPATH") 67 | if idx := strings.Index(gopath, oscmdenv.envsep); idx > 0 { 68 | gopath = gopath[:idx] 69 | } 70 | return filepath.Join(gopath, "src", "_weave", setBase()) 71 | } 72 | 73 | // build does the actual compilation 74 | // right nowe we piggy back off of 6g/8g 75 | func (w *Weave) build() { 76 | 77 | idx := strings.Index(w.buildLocation, "_weave") 78 | weavedir := w.buildLocation[:idx+6] 79 | defer os.RemoveAll(weavedir) 80 | 81 | delbin := fmt.Sprintf("%s %s", oscmdenv.rmdir, filepath.Join(w.buildLocation, w.binName())) 82 | execShellCmd(delbin) 83 | 84 | buildstr := "cd " + w.buildLocation 85 | buildstr += " && " + w.whichGo() + " build " 86 | buildstr += fmt.Sprintf(" && %s %s %s", oscmdenv.cp, w.binName(), w.buildDir()) 87 | 88 | o, err := execShellCmd(buildstr) 89 | if err != nil { 90 | w.flog.Println(string(o)) 91 | } 92 | } 93 | 94 | // prep prepares any tmp. build dirs 95 | func (w *Weave) prep() { 96 | 97 | // hacky dir prep 98 | os.RemoveAll(w.buildLocation) 99 | fstcmd := fmt.Sprintf("%s %s", oscmdenv.mkdir, w.buildLocation) 100 | 101 | // hack to get anything that might be ref'd in the env 102 | hackcmd := fmt.Sprintf("%s * %s", oscmdenv.xcp, w.buildLocation) 103 | 104 | _, err := execShellCmd(fstcmd) 105 | if err != nil { 106 | w.flog.Println(fstcmd, err.Error()) 107 | } 108 | 109 | filepath.Walk( 110 | w.buildLocation, 111 | func(fname string, fi os.FileInfo, err error) error { 112 | if fi == nil || err != nil { 113 | return err 114 | } else if !fi.IsDir() || fi.Name() == "." || fi.Name() == ".." { 115 | return nil 116 | } else { 117 | mkdir := fmt.Sprintf("%s %s", oscmdenv.mkdir, filepath.Join(w.buildLocation, fi.Name())) 118 | execShellCmd(mkdir) 119 | return nil 120 | } 121 | }, 122 | ) 123 | 124 | _, err = execShellCmd(hackcmd) 125 | if err != nil { 126 | w.flog.Println(hackcmd, err.Error()) 127 | } 128 | } 129 | 130 | // rootPkg returns the root package of a go build 131 | // this is needed to determine whether or not sub-pkg imports need to be 132 | // re-written - which is basically any project w/more than one folder 133 | func (w *Weave) rootPkg() string { 134 | return setBase() 135 | } 136 | 137 | // rootPkg returns the root package of a go build 138 | // this is needed to determine whether or not sub-pkg imports need to be 139 | // re-written - which is basically any project w/more than one folder 140 | func setBase() string { 141 | out, err := exec.Command("go", "list").CombinedOutput() 142 | if err != nil { 143 | log.Println(err.Error()) 144 | } 145 | 146 | str := strings.TrimSpace(string(out)) 147 | str = filepath.FromSlash(str) 148 | return str 149 | } 150 | -------------------------------------------------------------------------------- /weave/build_darwin.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | func init() { 4 | oscmdenv = &osCmdInfo{ 5 | shell: []string{"/bin/sh", "-c"}, 6 | mkdir: "mkdir -p", 7 | rmdir: "rm -rf", 8 | pwd: "pwd", 9 | cp: "cp", 10 | xcp: "cp -r", 11 | envsep: ":", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /weave/build_linux.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | func init() { 4 | oscmdenv = &osCmdInfo{ 5 | shell: []string{"/bin/sh", "-c"}, 6 | mkdir: "mkdir -p", 7 | rmdir: "rm -rf", 8 | pwd: "pwd", 9 | cp: "cp", 10 | xcp: "cp -r", 11 | envsep: ":", 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /weave/build_windows.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | func init() { 4 | oscmdenv = &osCmdInfo{ 5 | shell: []string{"cmd", "/e:on", "/c"}, 6 | mkdir: "mkdir", 7 | rmdir: "rmdir /s /q", 8 | pwd: "cd", 9 | cp: "copy", 10 | xcp: "xcopy /e /q", 11 | exeext: ".exe", 12 | envsep: ";", 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /weave/declaration.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "strings" 8 | ) 9 | 10 | // mAvars is a list of AbstractVars found 11 | // prob. needs to be refactored 12 | var mAvars []AbstractVar 13 | 14 | // AbstractVar contains the value of a var and it's type 15 | // Val should prob. be interface 16 | // refactor refactor refactor 17 | type AbstractVar struct { 18 | Kind string 19 | Val string 20 | } 21 | 22 | // applyDeclarationJP applies any advice for set joinpoints 23 | // currently expects a channel type 24 | // need some extra help here to be agnostic 25 | // maybe some helper functions that determine what type it is and 26 | // associated meta-data about it 27 | // 28 | // this is currently a TEST - it only works for specific channels at the 29 | // moment 30 | func (w *Weave) applyDeclarationJP(fname string, stuff string) string { 31 | 32 | rout := stuff 33 | 34 | importsNeeded := []string{} 35 | 36 | for i := 0; i < len(w.aspects); i++ { 37 | 38 | aspect := w.aspects[i] 39 | if !aspect.pointkut.isDeclaration() { 40 | continue 41 | } 42 | 43 | pk := aspect.pointkut.def 44 | 45 | fset := token.NewFileSet() 46 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 47 | if err != nil { 48 | w.flog.Println("Failed to parse source: %s", err.Error()) 49 | } 50 | 51 | linecnt := 0 52 | 53 | for _, decl := range file.Decls { 54 | fn, ok := decl.(*ast.FuncDecl) 55 | if !ok { 56 | continue 57 | } 58 | 59 | for x := 0; x < len(fn.Body.List); x++ { 60 | as, ok2 := fn.Body.List[x].(*ast.AssignStmt) 61 | if !ok2 { 62 | continue 63 | } 64 | 65 | // ll-cool-j 66 | blah := as.Lhs[0].(*ast.Ident).Name 67 | 68 | if pk != blah { 69 | continue 70 | } 71 | 72 | // figure out type 73 | 74 | // ll-cool-j once again 75 | if len(as.Rhs[0].(*ast.CallExpr).Args) == 2 { 76 | _, k := (as.Rhs[0].(*ast.CallExpr).Args[0]).(*ast.ChanType) 77 | if !k { 78 | continue 79 | } 80 | 81 | r2, k2 := (as.Rhs[0].(*ast.CallExpr).Args[1]).(*ast.BasicLit) 82 | if !k2 { 83 | continue 84 | } 85 | 86 | avar := AbstractVar{ 87 | Kind: "channel", 88 | Val: r2.Value, 89 | } 90 | 91 | begin := fset.Position(as.Pos()).Line - 1 92 | after := fset.Position(as.End()).Line + 1 93 | 94 | // this needs to do var subsitution like we do for 95 | // within advice 96 | // 97 | // before_advice := formatAdvice(aspect.advize.before, mName) 98 | // after_advice := formatAdvice(aspect.advize.after, mName) 99 | 100 | before_advice := aspect.advize.before 101 | after_advice := aspect.advize.after 102 | 103 | if before_advice != "" { 104 | rout = w.writeAtLine(fname, begin+linecnt, before_advice) 105 | linecnt += strings.Count(before_advice, "\n") + 1 106 | } 107 | 108 | if after_advice != "" { 109 | rout = w.writeAtLine(fname, after+linecnt-1, after_advice) 110 | 111 | linecnt += strings.Count(after_advice, "\n") + 1 112 | } 113 | 114 | mAvars = append(mAvars, avar) 115 | 116 | } 117 | 118 | } 119 | } 120 | 121 | } 122 | 123 | if len(importsNeeded) > 0 { 124 | // add any imports for this piece of advice 125 | rout = w.writeMissingImports(fname, rout, importsNeeded) 126 | } 127 | 128 | return rout 129 | } 130 | -------------------------------------------------------------------------------- /weave/declaration_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestApplyDeclarationJP(t *testing.T) { 9 | f1 := `package main 10 | 11 | import "fmt" 12 | 13 | func main() { 14 | ch := make(chan int, 2) 15 | ch <- 1 16 | ch <- 2 17 | fmt.Println(<-ch) 18 | fmt.Println(<-ch) 19 | } 20 | ` 21 | 22 | expected := `package main 23 | 24 | import "fmt" 25 | 26 | func main() { 27 | fmt.Println("yo joe") 28 | ch := make(chan int, 2) 29 | ch <- 1 30 | ch <- 2 31 | fmt.Println(<-ch) 32 | fmt.Println(<-ch) 33 | } 34 | ` 35 | 36 | w := &Weave{} 37 | 38 | w.writeOut(os.TempDir()+"/blah", f1) 39 | 40 | aspect := Aspect{ 41 | advize: Advice{ 42 | before: "fmt.Println(\"yo joe\")", 43 | }, 44 | pointkut: Pointcut{ 45 | def: "ch", 46 | kind: 6, 47 | }, 48 | } 49 | 50 | aspects := []Aspect{} 51 | aspects = append(aspects, aspect) 52 | w.aspects = aspects 53 | 54 | after := w.applyDeclarationJP(os.TempDir()+"/blah", f1) 55 | 56 | if after != expected { 57 | t.Error(after) 58 | t.Error(expected) 59 | t.Error("applyDeclarationJP is not transforming correctly") 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /weave/file.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "bufio" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "strings" 9 | ) 10 | 11 | // reWriteFile rewrites curfile with out && adds any missing imports 12 | func (w *Weave) reWriteFile(curfile string, out string, importsNeeded []string) { 13 | 14 | path := w.buildLocation + "/" + curfile 15 | 16 | f, err := os.Create(path) 17 | if err != nil { 18 | w.flog.Println(err) 19 | } 20 | 21 | defer f.Close() 22 | 23 | if len(importsNeeded) > 0 { 24 | out = w.addMissingImports(importsNeeded, out) 25 | } 26 | 27 | _, err = f.WriteString(out) 28 | if err != nil { 29 | w.flog.Println(err) 30 | } 31 | 32 | w.reWorkImports(path) 33 | 34 | } 35 | 36 | // fileAsStr reads path file and returns it as a string 37 | func fileAsStr(path string) string { 38 | buf, err := ioutil.ReadFile(path) 39 | if err != nil { 40 | log.Println(err) 41 | } 42 | 43 | return string(buf) 44 | } 45 | 46 | // returns a slice of lines from file path 47 | func fileLines(path string) []string { 48 | stuff := []string{} 49 | 50 | file, err := os.Open(path) 51 | 52 | if err != nil { 53 | log.Println(err) 54 | } 55 | 56 | defer file.Close() 57 | 58 | reader := bufio.NewReader(file) 59 | scanner := bufio.NewScanner(reader) 60 | 61 | for scanner.Scan() { 62 | stuff = append(stuff, scanner.Text()) 63 | } 64 | 65 | return stuff 66 | } 67 | 68 | // writeOut writes nlines to path 69 | func (w *Weave) writeOut(path string, nlines string) { 70 | 71 | b := []byte(nlines) 72 | err := ioutil.WriteFile(path, b, 0644) 73 | if err != nil { 74 | w.flog.Println(err) 75 | } 76 | } 77 | 78 | // writeAtLine inserts writes to fname lntxt @ iline 79 | func (w *Weave) writeAtLine(fname string, iline int, lntxt string) string { 80 | flines := fileLines(fname) 81 | 82 | out := "" 83 | for i := 0; i < len(flines); i++ { 84 | if i == iline { 85 | out += lntxt + "\n" 86 | } 87 | 88 | if strings.TrimSpace(flines[i]) == "" { 89 | out += "\n" 90 | } else { 91 | out += flines[i] + "\n" 92 | } 93 | } 94 | 95 | w.writeOut(fname, out) 96 | 97 | return out 98 | } 99 | -------------------------------------------------------------------------------- /weave/get.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "strings" 8 | ) 9 | 10 | // applyGetJP applies any advice for get joinpoints 11 | func (w *Weave) applyGetJP(fname string, stuff string) string { 12 | 13 | rout := stuff 14 | 15 | importsNeeded := []string{} 16 | 17 | for i := 0; i < len(w.aspects); i++ { 18 | 19 | aspect := w.aspects[i] 20 | if !aspect.pointkut.isGet() { 21 | continue 22 | } 23 | 24 | pk := aspect.pointkut.def 25 | 26 | fset := token.NewFileSet() 27 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 28 | if err != nil { 29 | w.flog.Println("Failed to parse source: %s", err.Error()) 30 | } 31 | 32 | linecnt := 0 33 | 34 | for _, decl := range file.Decls { 35 | fn, ok := decl.(*ast.FuncDecl) 36 | if !ok { 37 | continue 38 | } 39 | 40 | for x := 0; x < len(fn.Body.List); x++ { 41 | as, ok2 := fn.Body.List[x].(*ast.ExprStmt) 42 | if !ok2 { 43 | continue 44 | } 45 | 46 | blah, ok3 := as.X.(*ast.CallExpr) 47 | if !ok3 { 48 | continue 49 | } 50 | 51 | // can either be a unary || a ident (so far) 52 | fn2, ok4 := blah.Args[0].(*ast.UnaryExpr) 53 | if !ok4 { 54 | // look for ident 55 | 56 | fn3, ok5 := blah.Args[0].(*ast.Ident) 57 | if !ok5 { 58 | continue 59 | } 60 | 61 | if pk != fn3.Name { 62 | continue 63 | } 64 | 65 | } else { 66 | // look for channel 67 | 68 | blah2, ok4 := fn2.X.(*ast.Ident) 69 | if !ok4 { 70 | continue 71 | } 72 | 73 | if pk != blah2.Name { 74 | continue 75 | } 76 | } 77 | 78 | begin := fset.Position(as.Pos()).Line - 1 79 | after := fset.Position(as.End()).Line + 1 80 | 81 | before_advice := aspect.advize.before 82 | after_advice := aspect.advize.after 83 | 84 | if before_advice != "" { 85 | rout = w.writeAtLine(fname, begin+linecnt, before_advice) 86 | linecnt += strings.Count(before_advice, "\n") + 1 87 | } 88 | 89 | if after_advice != "" { 90 | rout = w.writeAtLine(fname, after+linecnt-1, after_advice) 91 | 92 | linecnt += strings.Count(after_advice, "\n") + 1 93 | } 94 | 95 | } 96 | 97 | } 98 | } 99 | 100 | if len(importsNeeded) > 0 { 101 | // add any imports for this piece of advice 102 | rout = w.writeMissingImports(fname, rout, importsNeeded) 103 | } 104 | 105 | return rout 106 | } 107 | -------------------------------------------------------------------------------- /weave/get_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestApplyGetJP(t *testing.T) { 9 | f1 := `package main 10 | 11 | import "fmt" 12 | 13 | func main() { 14 | ch := make(chan int, 2) 15 | ch <- 1 16 | ch <- 2 17 | fmt.Println(<-ch) 18 | fmt.Println(<-ch) 19 | } 20 | ` 21 | 22 | expected := `package main 23 | 24 | import "fmt" 25 | 26 | func main() { 27 | ch := make(chan int, 2) 28 | ch <- 1 29 | ch <- 2 30 | fmt.Println("yo joe") 31 | fmt.Println(<-ch) 32 | fmt.Println("yo joe") 33 | fmt.Println(<-ch) 34 | } 35 | ` 36 | 37 | w := &Weave{} 38 | 39 | w.writeOut(os.TempDir()+"/blah", f1) 40 | 41 | aspect := Aspect{ 42 | advize: Advice{ 43 | before: "fmt.Println(\"yo joe\")", 44 | }, 45 | pointkut: Pointcut{ 46 | def: "ch", 47 | kind: 4, 48 | }, 49 | } 50 | 51 | aspects := []Aspect{} 52 | aspects = append(aspects, aspect) 53 | w.aspects = aspects 54 | 55 | after := w.applyGetJP(os.TempDir()+"/blah", f1) 56 | 57 | if after != expected { 58 | t.Error(after) 59 | t.Error(expected) 60 | t.Error("applyGetJP is not transforming correctly") 61 | } 62 | 63 | } 64 | 65 | func TestApplySimpleGetJP(t *testing.T) { 66 | f1 := `package main 67 | 68 | import "fmt" 69 | 70 | func main() { 71 | x := "stuff" 72 | y := 2 73 | fmt.Println(x) 74 | fmt.Println(y) 75 | } 76 | ` 77 | 78 | expected := `package main 79 | 80 | import "fmt" 81 | 82 | func main() { 83 | x := "stuff" 84 | y := 2 85 | fmt.Println(x) 86 | fmt.Println("before get y") 87 | fmt.Println(y) 88 | } 89 | ` 90 | 91 | w := &Weave{} 92 | 93 | w.writeOut(os.TempDir()+"/blah", f1) 94 | 95 | aspect := Aspect{ 96 | advize: Advice{ 97 | before: "fmt.Println(\"before get y\")", 98 | }, 99 | pointkut: Pointcut{ 100 | def: "y", 101 | kind: 4, 102 | }, 103 | } 104 | 105 | aspects := []Aspect{} 106 | aspects = append(aspects, aspect) 107 | w.aspects = aspects 108 | 109 | after := w.applyGetJP(os.TempDir()+"/blah", f1) 110 | 111 | if after != expected { 112 | t.Error(after) 113 | t.Error(expected) 114 | t.Error("applyGetJP is not transforming correctly") 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /weave/go_routines.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "regexp" 7 | "strings" 8 | ) 9 | 10 | // returns true if this is a multi-line go routine 11 | func multiLineGo(l string) bool { 12 | if strings.Contains(l, "go func(") { 13 | return true 14 | } 15 | 16 | return false 17 | } 18 | 19 | // returns true if this is a single line go routine 20 | func singleLineGo(l string) bool { 21 | var singlelinego = regexp.MustCompile(`go\s.*\(.*\)`) 22 | if singlelinego.MatchString(l) { 23 | return true 24 | } 25 | 26 | return false 27 | } 28 | 29 | // processGoRoutines is in the process of being DEPRECATED 30 | // it only provides regex support for go before/after advice 31 | // once we refactor to AST replacing the go routines this function will 32 | // go away 33 | // this does not write to any files - simply manipulates text 34 | func (w *Weave) processGoRoutines(curfile string, rootpkg string) (string, bool) { 35 | modified := false 36 | 37 | file, err := os.Open(curfile) 38 | if err != nil { 39 | w.flog.Println(err) 40 | } 41 | defer file.Close() 42 | 43 | out := "" 44 | 45 | // FIXME 46 | // poor man's scope 47 | scope := 0 48 | 49 | cur_aspect := Aspect{} 50 | 51 | scanner := bufio.NewScanner(file) 52 | for scanner.Scan() { 53 | l := scanner.Text() 54 | 55 | newAspect := pointCutMatch(w.aspects, l) 56 | if newAspect.pointkut.def != "" { 57 | modified = true 58 | 59 | scope += 1 60 | 61 | cur_aspect = newAspect 62 | 63 | if cur_aspect.advize.before != "" { 64 | 65 | // go before advice 66 | if cur_aspect.pointkut.def == "go" { 67 | if multiLineGo(l) { 68 | 69 | // keep grabbing lines until we are back to 70 | // existing scope? 71 | stuff := "" 72 | nscope := 1 73 | for i := 0; ; i++ { 74 | scanner.Scan() 75 | l2 := scanner.Text() 76 | 77 | if strings.Contains(l2, "{") { 78 | nscope += 1 79 | } 80 | 81 | if strings.Contains(l2, "}") { 82 | nscope -= 1 83 | } 84 | 85 | if nscope == 0 { 86 | break 87 | } 88 | 89 | stuff += l2 + "\n" 90 | 91 | } 92 | 93 | out += "go func(){\n" + cur_aspect.advize.before + "\n" + stuff + 94 | "\n" + "}()\n" 95 | 96 | } else if singleLineGo(l) { 97 | 98 | // hack - ASTize me 99 | r := regexp.MustCompile("go\\s(.*)\\((.*)\\)") 100 | 101 | newstr := r.ReplaceAllString(l, "go func(){\n"+ 102 | cur_aspect.advize.before+"\n$1($2)\n"+"}()") 103 | 104 | out += newstr + "\n" 105 | 106 | } 107 | } else { 108 | // normal before advice 109 | // out += l + "\n" + cur_aspect.advize.before + "\n" 110 | } 111 | 112 | continue 113 | } 114 | 115 | } 116 | 117 | // dat scope 118 | if strings.Contains(l, "}") || strings.Contains(l, "return") { 119 | 120 | scope -= 1 121 | 122 | out += cur_aspect.advize.after + "\n" 123 | } 124 | 125 | out += l + "\n" 126 | 127 | } 128 | 129 | if err := scanner.Err(); err != nil { 130 | w.flog.Println(err) 131 | } 132 | 133 | return out, modified 134 | } 135 | -------------------------------------------------------------------------------- /weave/imports.go: -------------------------------------------------------------------------------- 1 | // this file needs to be cleaned up hardcore 2 | // we don't need 10 different ways of doing the same thing 3 | 4 | package weave 5 | 6 | import ( 7 | "go/ast" 8 | "strings" 9 | ) 10 | 11 | type weaveImport struct { 12 | path string 13 | nsa bool 14 | } 15 | 16 | // reads file && de-dupes imports 17 | func (w *Weave) reWorkImports(fp string) string { 18 | flines := fileLines(fp) 19 | 20 | af := w.ParseAST(fp) 21 | 22 | pruned := w.pruneImports(af, w.rootPkg()) 23 | lines := w.deDupeImports(fp, flines, pruned) 24 | 25 | w.writeOut(fp, lines) 26 | 27 | return lines 28 | } 29 | 30 | // deDupeImports de-dupes imports 31 | // this is txt processing - not from the ast - FIXME 32 | func (w *Weave) deDupeImports(path string, flines []string, pruned []*ast.ImportSpec) string { 33 | nlines := "" 34 | 35 | inImport := false 36 | 37 | for i := 0; i < len(flines); i++ { 38 | 39 | // see if we want to add any imports to the file 40 | if strings.Contains(flines[i], "import (") { 41 | inImport = true 42 | 43 | nlines += flines[i] + "\n" 44 | 45 | for x := 0; x < len(pruned); x++ { 46 | if pruned[x].Name != nil { 47 | nlines += pruned[x].Name.Name + " " + pruned[x].Path.Value + "\n" 48 | } else { 49 | nlines += pruned[x].Path.Value + "\n" 50 | } 51 | } 52 | 53 | // empty import check 54 | if strings.Contains(flines[i], "()") { 55 | inImport = false 56 | } 57 | 58 | continue 59 | } 60 | 61 | if inImport { 62 | if strings.Contains(flines[i], ")") { 63 | inImport = false 64 | 65 | nlines += ")" + "\n" 66 | } 67 | 68 | continue 69 | } 70 | 71 | // write out the original line 72 | nlines += flines[i] + "\n" 73 | 74 | } 75 | 76 | return nlines 77 | } 78 | 79 | // writeMissingImports writes body && any missing imports to fp 80 | func (w *Weave) writeMissingImports(fp string, out string, importsNeeded []string) string { 81 | 82 | out = w.addMissingImports(importsNeeded, out) 83 | 84 | w.writeOut(fp, out) 85 | 86 | // de-dupe imports 87 | return w.reWorkImports(fp) 88 | } 89 | 90 | // pruneImports returns a set of import strings de-duped from the ast 91 | func (w *Weave) pruneImports(f *ast.File, rootpkg string) []*ast.ImportSpec { 92 | pruned := []*ast.ImportSpec{} 93 | 94 | for i := 0; i < len(f.Imports); i++ { 95 | if f.Imports[i].Path != nil { 96 | 97 | l := f.Imports[i].Path.Value 98 | 99 | if strings.Contains(l, rootpkg) && !strings.Contains(l, "_weave") { 100 | f.Imports[i].Path.Value = rewriteImport(l, rootpkg) 101 | } 102 | 103 | if !inthere(f.Imports[i].Path.Value, pruned) { 104 | pruned = append(pruned, f.Imports[i]) 105 | } 106 | } 107 | } 108 | 109 | return pruned 110 | } 111 | 112 | // inthere returns true if p is part of ray 113 | func inthere(p string, ray []*ast.ImportSpec) bool { 114 | for i := 0; i < len(ray); i++ { 115 | if ray[i].Path.Value == p { 116 | return true 117 | } 118 | } 119 | 120 | return false 121 | } 122 | 123 | // addMissingImports adds any imports from advice that was found 124 | func (w *Weave) addMissingImports(imports []string, out string) string { 125 | 126 | if strings.Contains(out, "import (") { 127 | s := "\n" 128 | for i := 0; i < len(imports); i++ { 129 | s += imports[i] + "\n" 130 | } 131 | 132 | out = strings.Replace(out, "import (", "import ("+s, -1) 133 | } else { 134 | 135 | s := "" 136 | for i := 0; i < len(imports); i++ { 137 | s += "import " + imports[i] + "\n" 138 | } 139 | 140 | out = strings.Replace(out, "import ", s+"import", -1) 141 | } 142 | 143 | return out 144 | } 145 | 146 | // rewriteImport is intended to rewrite a sub pkg of the base pkg to a 147 | // relative path since we for now cp it to a diff. workspace 148 | func rewriteImport(l string, rp string) string { 149 | return "\"_weave/" + l[1:len(l)] 150 | } 151 | -------------------------------------------------------------------------------- /weave/pointcut.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "errors" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | // Pointcut describe how to apply a particular aspect 10 | type Pointcut struct { 11 | def string 12 | pkg string 13 | funktion string 14 | kind int 15 | } 16 | 17 | func (p Pointcut) isCall() bool { 18 | if p.kind == 1 { 19 | return true 20 | } else { 21 | return false 22 | } 23 | } 24 | 25 | func (p Pointcut) isExecute() bool { 26 | if p.kind == 2 { 27 | return true 28 | } else { 29 | return false 30 | } 31 | } 32 | 33 | func (p Pointcut) isWithin() bool { 34 | if p.kind == 3 { 35 | return true 36 | } else { 37 | return false 38 | } 39 | } 40 | 41 | func (p Pointcut) isGet() bool { 42 | if p.kind == 4 { 43 | return true 44 | } else { 45 | return false 46 | } 47 | } 48 | 49 | func (p Pointcut) isSet() bool { 50 | if p.kind == 5 { 51 | return true 52 | } else { 53 | return false 54 | } 55 | } 56 | 57 | func (p Pointcut) isDeclaration() bool { 58 | if p.kind == 6 { 59 | return true 60 | } else { 61 | return false 62 | } 63 | } 64 | 65 | // set def extracts the joinpoint from a pointcut definition 66 | func setDef(t string) (int, string, error) { 67 | 68 | m := `(execute|call|within|get|set|declaration)\((.*)\)` 69 | re, err := regexp.Compile(m) 70 | if err != nil { 71 | return 0, "", errors.New("bad regex") 72 | } 73 | 74 | res := re.FindAllStringSubmatch(t, -1) 75 | if len(res[0]) == 3 { 76 | if res[0][1] == "call" { 77 | return 1, res[0][2], nil 78 | } else if res[0][1] == "execute" { 79 | return 2, res[0][2], nil 80 | } else if res[0][1] == "within" { 81 | return 3, res[0][2], nil 82 | } else if res[0][1] == "get" { 83 | return 4, res[0][2], nil 84 | } else if res[0][1] == "set" { 85 | return 5, res[0][2], nil 86 | } else if res[0][1] == "declaration" { 87 | return 6, res[0][2], nil 88 | } else { 89 | return 0, "", errors.New("bad pointcut") 90 | } 91 | } else { 92 | return 0, "", errors.New("bad pointcut") 93 | } 94 | } 95 | 96 | // parsePointCut parses out a pointcut from an aspect 97 | func (w *Weave) parsePointCut(body string) (Pointcut, error) { 98 | pc := strings.Split(body, "pointcut:") 99 | 100 | if len(pc) > 1 { 101 | rpc := strings.Split(pc[1], "\n")[0] 102 | t := strings.TrimSpace(rpc) 103 | 104 | k, def, err := setDef(t) 105 | if err != nil { 106 | return Pointcut{}, err 107 | } 108 | 109 | return Pointcut{ 110 | def: def, 111 | funktion: def, 112 | kind: k, 113 | }, nil 114 | } else { 115 | return Pointcut{}, errors.New("invalid pointcut" + body) 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /weave/pointcut_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestSetDef(t *testing.T) { 8 | 9 | var pkTests = []struct { 10 | n string 11 | kind int 12 | def string 13 | }{ 14 | {"call(beforeBob)", 1, "beforeBob"}, 15 | {"execute(beforeBob)", 2, "beforeBob"}, 16 | {"within(beforeBob)", 3, "beforeBob"}, 17 | {"execute(FuncWithArgs(iarg int, sarg string))", 2, "FuncWithArgs(iarg int, sarg string)"}, 18 | {"execute(FuncWithArgsAndReturn(iarg int, sarg string) (int, error))", 2, "FuncWithArgsAndReturn(iarg int, sarg string) (int, error)"}, 19 | {"get(x)", 4, "x"}, 20 | {"set(ch)", 5, "ch"}, 21 | {"declaration(ch)", 6, "ch"}, 22 | } 23 | 24 | for _, tt := range pkTests { 25 | k, d, e := setDef(tt.n) 26 | if k != tt.kind { 27 | t.Errorf("wrong kind") 28 | } 29 | 30 | if d != tt.def { 31 | t.Error(d) 32 | t.Errorf("wrong def") 33 | } 34 | 35 | if e != nil { 36 | t.Error(e) 37 | } 38 | 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /weave/replace.go: -------------------------------------------------------------------------------- 1 | // This code is copied from the gofmt source at 2 | // http://golang.org/src/cmd/gofmt/rewrite.go 3 | 4 | // Copyright 2009 The Go Authors. All rights reserved. 5 | // Use of this source code is governed by a BSD-style 6 | // license that can be found in the LICENSE file. 7 | 8 | // not sure if we need to keep this in the future 9 | package weave 10 | 11 | import ( 12 | "go/ast" 13 | "go/token" 14 | "log" 15 | "reflect" 16 | "unicode" 17 | "unicode/utf8" 18 | ) 19 | 20 | func rewriteNoPat(replace ast.Expr, p *ast.File) *ast.File { 21 | cmap := ast.NewCommentMap(token.NewFileSet(), p, p.Comments) 22 | m := make(map[string]reflect.Value) 23 | repl := reflect.ValueOf(replace) 24 | 25 | var rewriteVal func(val reflect.Value) reflect.Value 26 | rewriteVal = func(val reflect.Value) reflect.Value { 27 | // don't bother if val is invalid to start with 28 | if !val.IsValid() { 29 | return reflect.Value{} 30 | } 31 | for k := range m { 32 | delete(m, k) 33 | } 34 | val = apply(rewriteVal, val) 35 | val = subst(m, repl, reflect.ValueOf(val.Interface().(ast.Node).Pos())) 36 | return val 37 | } 38 | 39 | r := apply(rewriteVal, reflect.ValueOf(p)).Interface().(*ast.File) 40 | r.Comments = cmap.Filter(r).Comments() // recreate comments list 41 | return r 42 | } 43 | 44 | func rewriteFile2(pattern, replace ast.Expr, p *ast.File) *ast.File { 45 | cmap := ast.NewCommentMap(token.NewFileSet(), p, p.Comments) 46 | m := make(map[string]reflect.Value) 47 | pat := reflect.ValueOf(pattern) 48 | repl := reflect.ValueOf(replace) 49 | 50 | var rewriteVal func(val reflect.Value) reflect.Value 51 | rewriteVal = func(val reflect.Value) reflect.Value { 52 | // don't bother if val is invalid to start with 53 | if !val.IsValid() { 54 | return reflect.Value{} 55 | } 56 | for k := range m { 57 | delete(m, k) 58 | } 59 | val = apply(rewriteVal, val) 60 | if match(m, pat, val) { 61 | val = subst(m, repl, reflect.ValueOf(val.Interface().(ast.Node).Pos())) 62 | } 63 | return val 64 | } 65 | 66 | r := apply(rewriteVal, reflect.ValueOf(p)).Interface().(*ast.File) 67 | r.Comments = cmap.Filter(r).Comments() // recreate comments list 68 | return r 69 | } 70 | 71 | // set is a wrapper for x.Set(y); it protects the caller from panics if x cannot be changed to y. 72 | func set(x, y reflect.Value) { 73 | // don't bother if x cannot be set or y is invalid 74 | if !x.CanSet() || !y.IsValid() { 75 | return 76 | } 77 | defer func() { 78 | if e := recover(); e != nil { 79 | log.Println("Failure while setting value %v to %v: %v", x, y, e) 80 | } 81 | }() 82 | x.Set(y) 83 | } 84 | 85 | // Values/types for special cases. 86 | var ( 87 | objectPtrNil = reflect.ValueOf((*ast.Object)(nil)) 88 | scopePtrNil = reflect.ValueOf((*ast.Scope)(nil)) 89 | 90 | identType = reflect.TypeOf((*ast.Ident)(nil)) 91 | objectPtrType = reflect.TypeOf((*ast.Object)(nil)) 92 | positionType = reflect.TypeOf(token.NoPos) 93 | callExprType = reflect.TypeOf((*ast.CallExpr)(nil)) 94 | scopePtrType = reflect.TypeOf((*ast.Scope)(nil)) 95 | ) 96 | 97 | // apply replaces each AST field x in val with f(x), returning val. 98 | // To avoid extra conversions, f operates on the reflect.Value form. 99 | func apply(f func(reflect.Value) reflect.Value, val reflect.Value) reflect.Value { 100 | if !val.IsValid() { 101 | return reflect.Value{} 102 | } 103 | 104 | // *ast.Objects introduce cycles and are likely incorrect after 105 | // rewrite; don't follow them but replace with nil instead 106 | if val.Type() == objectPtrType { 107 | return objectPtrNil 108 | } 109 | 110 | // similarly for scopes: they are likely incorrect after a rewrite; 111 | // replace them with nil 112 | if val.Type() == scopePtrType { 113 | return scopePtrNil 114 | } 115 | 116 | switch v := reflect.Indirect(val); v.Kind() { 117 | case reflect.Slice: 118 | for i := 0; i < v.Len(); i++ { 119 | e := v.Index(i) 120 | set(e, f(e)) 121 | } 122 | case reflect.Struct: 123 | for i := 0; i < v.NumField(); i++ { 124 | e := v.Field(i) 125 | set(e, f(e)) 126 | } 127 | case reflect.Interface: 128 | e := v.Elem() 129 | set(v, f(e)) 130 | } 131 | return val 132 | } 133 | 134 | func isWildcard(s string) bool { 135 | rune, size := utf8.DecodeRuneInString(s) 136 | return size == len(s) && unicode.IsLower(rune) 137 | } 138 | 139 | // subst returns a copy of pattern with values from m substituted in 140 | // place 141 | // of wildcards and pos used as the position of tokens from the pattern. 142 | // if m == nil, subst returns a copy of pattern and doesn't change the 143 | // line 144 | // number information. 145 | func subst(m map[string]reflect.Value, pattern reflect.Value, pos reflect.Value) reflect.Value { 146 | if !pattern.IsValid() { 147 | return reflect.Value{} 148 | } 149 | 150 | // Wildcard gets replaced with map value. 151 | if m != nil && pattern.Type() == identType { 152 | name := pattern.Interface().(*ast.Ident).Name 153 | if isWildcard(name) { 154 | if old, ok := m[name]; ok { 155 | return subst(nil, old, reflect.Value{}) 156 | } 157 | } 158 | } 159 | 160 | if pos.IsValid() && pattern.Type() == positionType { 161 | // use new position only if old position was valid in the first 162 | // place 163 | if old := pattern.Interface().(token.Pos); !old.IsValid() { 164 | return pattern 165 | } 166 | return pos 167 | } 168 | 169 | // Otherwise copy. 170 | switch p := pattern; p.Kind() { 171 | case reflect.Slice: 172 | v := reflect.MakeSlice(p.Type(), p.Len(), p.Len()) 173 | for i := 0; i < p.Len(); i++ { 174 | v.Index(i).Set(subst(m, p.Index(i), pos)) 175 | } 176 | return v 177 | 178 | case reflect.Struct: 179 | v := reflect.New(p.Type()).Elem() 180 | for i := 0; i < p.NumField(); i++ { 181 | v.Field(i).Set(subst(m, p.Field(i), pos)) 182 | } 183 | return v 184 | 185 | case reflect.Ptr: 186 | v := reflect.New(p.Type()).Elem() 187 | if elem := p.Elem(); elem.IsValid() { 188 | v.Set(subst(m, elem, pos).Addr()) 189 | } 190 | return v 191 | 192 | case reflect.Interface: 193 | v := reflect.New(p.Type()).Elem() 194 | if elem := p.Elem(); elem.IsValid() { 195 | v.Set(subst(m, elem, pos)) 196 | } 197 | return v 198 | } 199 | 200 | return pattern 201 | } 202 | 203 | // match returns true if pattern matches val, 204 | // recording wildcard submatches in m. 205 | // If m == nil, match checks whether pattern == val. 206 | func match(m map[string]reflect.Value, pattern, val reflect.Value) bool { 207 | // Wildcard matches any expression. If it appears multiple 208 | // times in the pattern, it must match the same expression 209 | // each time. 210 | if m != nil && pattern.IsValid() && pattern.Type() == identType { 211 | name := pattern.Interface().(*ast.Ident).Name 212 | if isWildcard(name) && val.IsValid() { 213 | // wildcards only match valid (non-nil) expressions. 214 | if _, ok := val.Interface().(ast.Expr); ok && !val.IsNil() { 215 | if old, ok := m[name]; ok { 216 | return match(nil, old, val) 217 | } 218 | m[name] = val 219 | return true 220 | } 221 | } 222 | } 223 | 224 | // Otherwise, pattern and val must match recursively. 225 | if !pattern.IsValid() || !val.IsValid() { 226 | return !pattern.IsValid() && !val.IsValid() 227 | } 228 | if pattern.Type() != val.Type() { 229 | return false 230 | } 231 | 232 | // Special cases. 233 | switch pattern.Type() { 234 | case identType: 235 | // For identifiers, only the names need to match 236 | // (and none of the other *ast.Object information). 237 | // This is a common case, handle it all here instead 238 | // of recursing down any further via reflection. 239 | p := pattern.Interface().(*ast.Ident) 240 | v := val.Interface().(*ast.Ident) 241 | return p == nil && v == nil || p != nil && v != nil && p.Name == v.Name 242 | case objectPtrType, positionType: 243 | // object pointers and token positions always match 244 | return true 245 | case callExprType: 246 | // For calls, the Ellipsis fields (token.Position) must 247 | // match since that is how f(x) and f(x...) are different. 248 | // Check them here but fall through for the remaining fields. 249 | p := pattern.Interface().(*ast.CallExpr) 250 | v := val.Interface().(*ast.CallExpr) 251 | if p.Ellipsis.IsValid() != v.Ellipsis.IsValid() { 252 | return false 253 | } 254 | } 255 | 256 | p := reflect.Indirect(pattern) 257 | v := reflect.Indirect(val) 258 | if !p.IsValid() || !v.IsValid() { 259 | return !p.IsValid() && !v.IsValid() 260 | } 261 | 262 | switch p.Kind() { 263 | case reflect.Slice: 264 | if p.Len() != v.Len() { 265 | return false 266 | } 267 | for i := 0; i < p.Len(); i++ { 268 | if !match(m, p.Index(i), v.Index(i)) { 269 | return false 270 | } 271 | } 272 | return true 273 | 274 | case reflect.Struct: 275 | for i := 0; i < p.NumField(); i++ { 276 | if !match(m, p.Field(i), v.Field(i)) { 277 | return false 278 | } 279 | } 280 | return true 281 | 282 | case reflect.Interface: 283 | return match(m, p.Elem(), v.Elem()) 284 | } 285 | 286 | // Handle token integers, etc. 287 | return p.Interface() == v.Interface() 288 | } 289 | -------------------------------------------------------------------------------- /weave/set.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "strings" 8 | ) 9 | 10 | // applySetJP applies any advice for set joinpoints 11 | func (w *Weave) applySetJP(fname string, stuff string) string { 12 | 13 | rout := stuff 14 | 15 | importsNeeded := []string{} 16 | 17 | for i := 0; i < len(w.aspects); i++ { 18 | 19 | aspect := w.aspects[i] 20 | if !aspect.pointkut.isSet() { 21 | continue 22 | } 23 | 24 | pk := aspect.pointkut.def 25 | 26 | fset := token.NewFileSet() 27 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 28 | if err != nil { 29 | w.flog.Println("Failed to parse source: %s", err.Error()) 30 | } 31 | 32 | linecnt := 0 33 | 34 | for _, decl := range file.Decls { 35 | fn, ok := decl.(*ast.FuncDecl) 36 | if !ok { 37 | continue 38 | } 39 | 40 | for x := 0; x < len(fn.Body.List); x++ { 41 | begin := 0 42 | after := 0 43 | 44 | as, ok2 := fn.Body.List[x].(*ast.SendStmt) 45 | if !ok2 { 46 | 47 | // look for assignment 48 | //*ast.AssignStmt 49 | as, ok3 := fn.Body.List[x].(*ast.AssignStmt) 50 | if !ok3 { 51 | continue 52 | } 53 | 54 | // no multiple-return support yet 55 | blah := as.Lhs[0].(*ast.Ident).Name 56 | 57 | if pk != blah { 58 | continue 59 | } 60 | 61 | begin = fset.Position(as.Pos()).Line - 1 62 | after = fset.Position(as.End()).Line + 1 63 | 64 | } else { 65 | // look for channel 66 | as3, ok3 := as.Chan.(*ast.Ident) 67 | if !ok3 { 68 | continue 69 | } 70 | 71 | if as3.Name != pk { 72 | continue 73 | } 74 | 75 | begin = fset.Position(as.Pos()).Line - 1 76 | after = fset.Position(as.End()).Line + 1 77 | 78 | } 79 | 80 | // figure out type 81 | 82 | // begin := fset.Position(as.Pos()).Line - 1 83 | // after := fset.Position(as.End()).Line + 1 84 | 85 | before_advice := aspect.advize.before 86 | after_advice := aspect.advize.after 87 | 88 | if before_advice != "" { 89 | rout = w.writeAtLine(fname, begin+linecnt, before_advice) 90 | linecnt += strings.Count(before_advice, "\n") + 1 91 | } 92 | 93 | if after_advice != "" { 94 | rout = w.writeAtLine(fname, after+linecnt-1, after_advice) 95 | 96 | linecnt += strings.Count(after_advice, "\n") + 1 97 | } 98 | 99 | } 100 | } 101 | 102 | } 103 | 104 | if len(importsNeeded) > 0 { 105 | // add any imports for this piece of advice 106 | rout = w.writeMissingImports(fname, rout, importsNeeded) 107 | } 108 | 109 | return rout 110 | } 111 | -------------------------------------------------------------------------------- /weave/set_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestApplySetJP(t *testing.T) { 9 | f1 := `package main 10 | 11 | import "fmt" 12 | 13 | func main() { 14 | ch := make(chan int, 2) 15 | ch <- 1 16 | ch <- 2 17 | fmt.Println(<-ch) 18 | fmt.Println(<-ch) 19 | } 20 | ` 21 | 22 | expected := `package main 23 | 24 | import "fmt" 25 | 26 | func main() { 27 | fmt.Println("yo joe") 28 | ch := make(chan int, 2) 29 | fmt.Println("yo joe") 30 | ch <- 1 31 | fmt.Println("yo joe") 32 | ch <- 2 33 | fmt.Println(<-ch) 34 | fmt.Println(<-ch) 35 | } 36 | ` 37 | 38 | w := &Weave{} 39 | 40 | w.writeOut(os.TempDir()+"/blah", f1) 41 | 42 | aspect := Aspect{ 43 | advize: Advice{ 44 | before: "fmt.Println(\"yo joe\")", 45 | }, 46 | pointkut: Pointcut{ 47 | def: "ch", 48 | kind: 5, 49 | }, 50 | } 51 | 52 | aspects := []Aspect{} 53 | aspects = append(aspects, aspect) 54 | w.aspects = aspects 55 | 56 | after := w.applySetJP(os.TempDir()+"/blah", f1) 57 | 58 | if after != expected { 59 | t.Error(after) 60 | t.Error(expected) 61 | t.Error("applySetJP is not transforming correctly") 62 | } 63 | 64 | } 65 | 66 | func TestApplySimpleSetJP(t *testing.T) { 67 | f1 := `package main 68 | 69 | import "fmt" 70 | 71 | func main() { 72 | x := "stuff" 73 | y := 2 74 | fmt.Println(x) 75 | fmt.Println(y) 76 | } 77 | ` 78 | 79 | expected := `package main 80 | 81 | import "fmt" 82 | 83 | func main() { 84 | fmt.Println("before set x") 85 | x := "stuff" 86 | y := 2 87 | fmt.Println(x) 88 | fmt.Println(y) 89 | } 90 | ` 91 | 92 | w := &Weave{} 93 | 94 | w.writeOut(os.TempDir()+"/blah", f1) 95 | 96 | aspect := Aspect{ 97 | advize: Advice{ 98 | before: "fmt.Println(\"before set x\")", 99 | }, 100 | pointkut: Pointcut{ 101 | def: "x", 102 | kind: 5, 103 | }, 104 | } 105 | 106 | aspects := []Aspect{} 107 | aspects = append(aspects, aspect) 108 | w.aspects = aspects 109 | 110 | after := w.applySetJP(os.TempDir()+"/blah", f1) 111 | 112 | if after != expected { 113 | t.Error(after) 114 | t.Error(expected) 115 | t.Error("applySetJP is not transforming correctly") 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /weave/transform.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "bytes" 5 | "go/ast" 6 | "go/format" 7 | "go/parser" 8 | "go/token" 9 | "strings" 10 | ) 11 | 12 | // applyGlobalAdvice applies any global scoped advice 13 | // if advice has already been placed in this pkg than we skip 14 | // no support for sub-pkgs yet 15 | // FIXME 16 | func (w *Weave) applyGlobalAdvice(fname string, stuff string) string { 17 | if w.appliedGlobal { 18 | return stuff 19 | } 20 | 21 | rout := stuff 22 | 23 | importsNeeded := []string{} 24 | 25 | for i := 0; i < len(w.aspects); i++ { 26 | aspect := w.aspects[i] 27 | if aspect.pointkut.def != "*" { 28 | continue 29 | } 30 | 31 | before_advice := aspect.advize.before 32 | after_advice := aspect.advize.after 33 | 34 | w.appliedGlobal = true 35 | 36 | fset := token.NewFileSet() 37 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 38 | if err != nil { 39 | w.flog.Println("Failed to parse source: %s", err.Error()) 40 | } 41 | 42 | // endOfImport returns the line after the import statement 43 | // used to add global advice 44 | // just a guess here - doubt this is ordered 45 | ilen := len(file.Imports) 46 | s := file.Imports[ilen-1].End() 47 | ei := fset.Position(s).Line + 1 48 | 49 | if before_advice != "" { 50 | rout = w.writeAtLine(fname, ei, before_advice) 51 | } 52 | 53 | if after_advice != "" { 54 | rout = w.writeAtLine(fname, ei, after_advice) 55 | } 56 | 57 | } 58 | 59 | if len(importsNeeded) > 0 { 60 | rout = w.writeMissingImports(fname, rout, importsNeeded) 61 | } 62 | 63 | return rout 64 | } 65 | 66 | // applyCallAdvice applies call advice before/after a call 67 | // around advice is currently hacked in via applyAroundAdvice 68 | func (w *Weave) applyCallAdvice(fname string, stuff string) string { 69 | 70 | rout := stuff 71 | 72 | importsNeeded := []string{} 73 | 74 | for i := 0; i < len(w.aspects); i++ { 75 | aspect := w.aspects[i] 76 | if aspect.pointkut.kind != 1 { 77 | continue 78 | } 79 | 80 | linecnt := 0 81 | 82 | pk := aspect.pointkut.def 83 | before_advice := aspect.advize.before 84 | after_advice := aspect.advize.after 85 | 86 | fset := token.NewFileSet() 87 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 88 | if err != nil { 89 | w.flog.Println("Failed to parse source:", err.Error()) 90 | } 91 | 92 | // look for call expressions - call joinpoints 93 | 94 | // bend keeps track of tailing binaryExprs 95 | // if a call-expression's end is not > then we use this 96 | lastbinstart := 0 97 | lastbinend := 0 98 | 99 | ast.Inspect(file, func(n ast.Node) bool { 100 | 101 | switch stmt := n.(type) { 102 | 103 | case *ast.CompositeLit: 104 | // log.Printf("found a composite lit\n") 105 | lbs := fset.Position(stmt.Lbrace).Line 106 | lbe := fset.Position(stmt.Rbrace).Line 107 | 108 | if lbs > lastbinstart { 109 | lastbinstart = lbs 110 | } 111 | 112 | if lbe > lastbinend { 113 | lastbinend = lbe 114 | } 115 | 116 | case *ast.BinaryExpr: 117 | // child nodes traversed in DFS - so we want the max 118 | // it's ok when newer nodes re-write this out of scope 119 | lbs := fset.Position(stmt.X.Pos()).Line 120 | lbe := fset.Position(stmt.Y.Pos()).Line 121 | 122 | if lbs > lastbinstart { 123 | lastbinstart = lbs 124 | } 125 | 126 | if lbe > lastbinend { 127 | lastbinend = lbe 128 | } 129 | 130 | // log.Printf("last bin start %d, last bin end %d", lastbinstart, lastbinend) 131 | 132 | case *ast.CallExpr: 133 | var name string 134 | switch x := stmt.Fun.(type) { 135 | case *ast.Ident: 136 | name = x.Name 137 | case *ast.SelectorExpr: 138 | name = x.Sel.Name 139 | default: 140 | name = "WRONG" 141 | } 142 | 143 | pk = strings.Split(pk, "(")[0] 144 | 145 | // fixme - hack - we need support for identifying call 146 | // expressions w/pkgs 147 | if strings.Contains(pk, ".") { 148 | pk = strings.Split(pk, ".")[1] 149 | } 150 | 151 | // are we part of a bigger expression? 152 | // if so grab our lines so we don't erroneously set them 153 | if (string(name) != pk) && (len(stmt.Args) > 1) { 154 | // log.Printf("found comma..") 155 | // child nodes traversed in DFS - so we want the max 156 | // it's ok when newer nodes re-write this out of scope 157 | lbs := fset.Position(stmt.Lparen).Line 158 | lbe := fset.Position(stmt.Rparen).Line 159 | // log.Printf("lbs: %d, lbe: %d\n", lbs, lbe) 160 | if lbs > 0 { 161 | //lastbinstart { 162 | lastbinstart = lbs 163 | } 164 | 165 | if lbe > lastbinend { 166 | lastbinend = lbe 167 | } 168 | 169 | } 170 | 171 | if string(name) == pk { 172 | 173 | begin := fset.Position(stmt.Lparen).Line 174 | end := fset.Position(stmt.Rparen).Line 175 | // log.Printf("found expression on line %d\n", begin) 176 | //log.Printf("lbs: %d, lbe: %d\n", lbs, lbe) 177 | 178 | // adjust begin 179 | if begin > lastbinend { 180 | // log.Printf("using this funcs start %d", begin) 181 | } else { 182 | if lastbinstart < begin { 183 | begin = lastbinstart 184 | } 185 | // log.Printf("using binexps' start %d", begin) 186 | } 187 | 188 | if end > lastbinend { 189 | // log.Printf("using this funcs begin %d", begin) 190 | } else { 191 | if lastbinstart < begin { 192 | begin = lastbinstart 193 | } 194 | // log.Printf("using binexps' start %d", begin) 195 | } 196 | 197 | // adjust end 198 | if lastbinend > end { 199 | end = lastbinend 200 | } 201 | 202 | if before_advice != "" { 203 | rout = w.writeAtLine(fname, begin+linecnt-1, before_advice) 204 | // log.Println(rout) 205 | // log.Printf("writing at line %d", begin+linecnt-1) 206 | linecnt += strings.Count(before_advice, "\n") + 1 207 | } 208 | 209 | if after_advice != "" { 210 | rout = w.writeAtLine(fname, end+linecnt, after_advice) 211 | // log.Println(rout) 212 | // log.Printf("writing at line %d", end+linecnt) 213 | 214 | linecnt += strings.Count(after_advice, "\n") + 1 215 | } 216 | 217 | for t := 0; t < len(aspect.importz); t++ { 218 | importsNeeded = append(importsNeeded, aspect.importz[t]) 219 | } 220 | 221 | } 222 | } 223 | 224 | return true 225 | }) 226 | 227 | } 228 | 229 | if len(importsNeeded) > 0 { 230 | rout = w.writeMissingImports(fname, rout, importsNeeded) 231 | } 232 | 233 | return rout 234 | 235 | } 236 | 237 | // applyAroundAdvice uses code from gofmt to wrap any after advice 238 | // essentially this is the same stuff you could do w/the cmdline tool, 239 | // gofmt 240 | // 241 | // FIXME - mv to CallExpr 242 | // 243 | // looks for call joinpoints && provides around advice capability 244 | // 245 | // this is currently a hack to support deferpanic's http lib 246 | func (w *Weave) applyAroundAdvice(fname string) string { 247 | 248 | stuff := fileAsStr(fname) 249 | 250 | importsNeeded := []string{} 251 | 252 | for i := 0; i < len(w.aspects); i++ { 253 | aspect := w.aspects[i] 254 | if aspect.advize.around != "" { 255 | pk := aspect.pointkut 256 | around_advice := aspect.advize.around 257 | 258 | fset := token.NewFileSet() 259 | file, err := parser.ParseFile(fset, fname, stuff, parser.Mode(0)) 260 | if err != nil { 261 | w.flog.Println("Failed to parse source: %s", err.Error()) 262 | } 263 | 264 | // needs match groups 265 | // wildcards of d,s...etc. 266 | p := parseExpr(pk.def) 267 | val := parseExpr(around_advice) 268 | 269 | file = rewriteFile2(p, val, file) 270 | 271 | buf := new(bytes.Buffer) 272 | err = format.Node(buf, fset, file) 273 | if err != nil { 274 | w.flog.Println("Failed to format post-replace source: %v", err) 275 | } 276 | 277 | actual := buf.String() 278 | 279 | w.writeOut(fname, actual) 280 | 281 | stuff = actual 282 | 283 | for t := 0; t < len(aspect.importz); t++ { 284 | importsNeeded = append(importsNeeded, aspect.importz[t]) 285 | } 286 | 287 | } 288 | } 289 | 290 | if len(importsNeeded) > 0 { 291 | // add any imports for this piece of advice 292 | stuff = w.writeMissingImports(fname, stuff, importsNeeded) 293 | } 294 | 295 | return stuff 296 | } 297 | 298 | // applyExecutionJP applies any advice for execution joinpoints 299 | func (w *Weave) applyExecutionJP(fname string, stuff string) string { 300 | 301 | rout := stuff 302 | 303 | importsNeeded := []string{} 304 | 305 | for i := 0; i < len(w.aspects); i++ { 306 | aspect := w.aspects[i] 307 | if aspect.pointkut.kind != 2 { 308 | continue 309 | } 310 | 311 | pk := aspect.pointkut.def 312 | 313 | before_advice := aspect.advize.before 314 | after_advice := aspect.advize.after 315 | 316 | fset := token.NewFileSet() 317 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 318 | if err != nil { 319 | w.flog.Println("Failed to parse source: %s", err.Error()) 320 | } 321 | 322 | linecnt := 0 323 | 324 | // look for function declarations - ala look for execution 325 | // joinpoints 326 | for _, decl := range file.Decls { 327 | fn, ok := decl.(*ast.FuncDecl) 328 | if !ok { 329 | continue 330 | } 331 | 332 | fpk := strings.Split(pk, "(")[0] 333 | 334 | // if function name missing --> wildcard 335 | if fpk == "" { 336 | fpk = fn.Name.Name 337 | } 338 | 339 | if fn.Name.Name == fpk && containArgs(pk, fn.Type.Params.List) { 340 | 341 | // begin line 342 | begin := fset.Position(fn.Body.Lbrace).Line 343 | after := fset.Position(fn.Body.Rbrace).Line 344 | 345 | // until this is refactored - any lines we add in our 346 | // advice need to be accounted for w/begin 347 | 348 | if before_advice != "" { 349 | rout = w.writeAtLine(fname, begin+linecnt, before_advice) 350 | linecnt += strings.Count(before_advice, "\n") + 1 351 | } 352 | 353 | if after_advice != "" { 354 | if fn.Type.Results != nil { 355 | lcnt := strings.Count(after_advice, "\n") + 1 356 | rout = w.writeAtLine(fname, after+linecnt-1-lcnt, after_advice) 357 | } else { 358 | rout = w.writeAtLine(fname, after+linecnt-1, after_advice) 359 | } 360 | 361 | linecnt += strings.Count(after_advice, "\n") + 1 362 | } 363 | 364 | for t := 0; t < len(aspect.importz); t++ { 365 | importsNeeded = append(importsNeeded, aspect.importz[t]) 366 | } 367 | 368 | } 369 | } 370 | 371 | } 372 | 373 | if len(importsNeeded) > 0 { 374 | // add any imports for this piece of advice applyExecutionJP 375 | rout = w.writeMissingImports(fname, rout, importsNeeded) 376 | } 377 | 378 | return rout 379 | } 380 | -------------------------------------------------------------------------------- /weave/weave.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/types" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | 11 | "go/ast" 12 | "go/parser" 13 | "go/token" 14 | 15 | "golang.org/x/tools/go/loader" 16 | ) 17 | 18 | // Weave is struct runner for aspect transforms 19 | type Weave struct { 20 | flog *log.Logger 21 | aspects []Aspect 22 | 23 | // warn if AST parsing warns you 24 | // off by default as many times we don't care 25 | warnAST bool 26 | 27 | // the pkg where we run goweave 28 | basePkg string 29 | 30 | // build location is where weave our aspects 31 | buildLocation string 32 | 33 | // verbose debugging output 34 | verbose bool 35 | 36 | // appliedGlobal is a HACK - the goal is to keep track of whether or 37 | // not we have kept tracked of advice in a pkg so far - this is 38 | // currently not working as expected as sub-pkgs are ignored 39 | appliedGlobal bool 40 | } 41 | 42 | // NewWeave instantiates and returns a new aop 43 | func NewWeave() *Weave { 44 | 45 | w := &Weave{ 46 | flog: log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile), 47 | basePkg: setBase(), 48 | buildLocation: tmpLocation(), 49 | } 50 | 51 | return w 52 | 53 | } 54 | 55 | // Run preps, grabs advice, transforms the src, and builds the code 56 | func (w *Weave) Run() { 57 | w.prep() 58 | w.loadAspects() 59 | 60 | // old regex parsing 61 | // only used for go routines currently 62 | // soon to be DEPRECATED 63 | w.transform() 64 | 65 | // applys around advice && evals execution joinpoints 66 | filepath.Walk(w.buildLocation, w.VisitFile) 67 | 68 | w.build() 69 | 70 | } 71 | 72 | // VisitFile walks each file and transforms it's 73 | // this is fairly heavy/expensive/pos right now 74 | func (w *Weave) VisitFile(fp string, fi os.FileInfo, err error) error { 75 | 76 | if fi == nil || err != nil { 77 | return err 78 | } 79 | 80 | matched, err := filepath.Match("*.go", fi.Name()) 81 | if err != nil { 82 | w.flog.Println(err) 83 | return err 84 | } 85 | 86 | if matched { 87 | 88 | // provides 'around' style advice 89 | // HACK - should be moved to call advice 90 | stuff := w.applyAroundAdvice(fp) 91 | w.writeOut(fp, stuff) 92 | 93 | // any global advice ? stick it in this file 94 | // FIXME 95 | stuff = w.applyGlobalAdvice(fp, stuff) 96 | w.writeOut(fp, stuff) 97 | 98 | // provides advice matching against call join points 99 | stuff = w.applyCallAdvice(fp, stuff) 100 | w.writeOut(fp, stuff) 101 | 102 | // provides advice matching against execution join points 103 | stuff = w.applyExecutionJP(fp, stuff) 104 | w.writeOut(fp, stuff) 105 | 106 | // provides advice matching against within join points 107 | stuff = w.applyWithinJP(fp, stuff) 108 | w.writeOut(fp, stuff) 109 | 110 | // provides advice matching against declaration join points 111 | stuff = w.applyDeclarationJP(fp, stuff) 112 | w.writeOut(fp, stuff) 113 | 114 | // provides advice matching against set join points 115 | stuff = w.applySetJP(fp, stuff) 116 | w.writeOut(fp, stuff) 117 | 118 | // provides advice matching against get join points 119 | stuff = w.applyGetJP(fp, stuff) 120 | w.writeOut(fp, stuff) 121 | 122 | // finally re-work imports on each 123 | // to ensure files we didn't apply aspects to have correct 124 | // imports (eg: main.go) 125 | w.reWorkImports(fp) 126 | 127 | } 128 | 129 | return nil 130 | } 131 | 132 | // Parse parses the ast for this file and returns a ParsedFile 133 | func (w *Weave) ParseAST(fname string) *ast.File { 134 | var err error 135 | 136 | fset := token.NewFileSet() 137 | af, err := parser.ParseFile(fset, fname, nil, 0) 138 | if err != nil { 139 | w.flog.Println(err) 140 | } 141 | 142 | loadcfg := loader.Config{} 143 | loadcfg.CreateFromFilenames(fname) 144 | 145 | info := types.Info{ 146 | Types: make(map[ast.Expr]types.TypeAndValue), 147 | Defs: make(map[*ast.Ident]types.Object), 148 | } 149 | 150 | var conf types.Config 151 | _, err = conf.Check(af.Name.Name, fset, []*ast.File{af}, &info) 152 | if err != nil { 153 | if w.warnAST { 154 | w.flog.Println(err) 155 | } 156 | } 157 | 158 | return af 159 | } 160 | 161 | // pointCutMatch returns an aspect if there is a pointcut match on this 162 | // line or returns an empty aspect 163 | func pointCutMatch(a []Aspect, l string) Aspect { 164 | for i := 0; i < len(a); i++ { 165 | 166 | // look for go-routines 167 | if strings.Contains(l, "go ") && ("go" == a[i].pointkut.def) { 168 | return a[i] 169 | } 170 | 171 | } 172 | 173 | return Aspect{} 174 | } 175 | 176 | // transform reads line by line over each src file and inserts advice 177 | // where appropriate 178 | // 179 | // only inserts before/advice after 180 | func (w *Weave) transform() { 181 | 182 | fzs := w.findGoFiles() 183 | 184 | rootpkg := w.rootPkg() 185 | 186 | for i := 0; i < len(fzs); i++ { 187 | out, b := w.processGoRoutines(fzs[i], rootpkg) 188 | 189 | // FIXME 190 | if b { 191 | w.reWriteFile(fzs[i], out, []string{}) 192 | } 193 | } 194 | 195 | } 196 | 197 | // findGoFiles recursively finds all go files in a project 198 | func (w *Weave) findGoFiles() []string { 199 | res := []string{} 200 | 201 | visit := func(path string, f os.FileInfo, err error) error { 202 | if strings.Contains(path, ".go") { 203 | res = append(res, path) 204 | } 205 | return nil 206 | } 207 | 208 | err := filepath.Walk(".", visit) 209 | if err != nil { 210 | w.flog.Println(err.Error()) 211 | } 212 | 213 | return res 214 | } 215 | 216 | // findAspects finds all aspects for this project 217 | func (w *Weave) findAspects() []string { 218 | aspects := []string{} 219 | 220 | files, _ := ioutil.ReadDir("./") 221 | for _, f := range files { 222 | if strings.HasSuffix(f.Name(), ".weave") { 223 | aspects = append(aspects, f.Name()) 224 | log.Println(f.Name()) 225 | } 226 | } 227 | 228 | return aspects 229 | } 230 | -------------------------------------------------------------------------------- /weave/weave_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "os" 6 | "strconv" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | func TestApplyAroundAdvice(t *testing.T) { 12 | 13 | f1 := `package main 14 | 15 | import ( 16 | "net/http" 17 | ) 18 | 19 | func main() { 20 | http.HandleFunc("/panic", panicHandler) 21 | http.HandleFunc("/panic2", panic2Handler) 22 | } 23 | ` 24 | 25 | expected := `package main 26 | 27 | import ( 28 | "net/http" 29 | ) 30 | 31 | func main() { 32 | http.HandleFunc("/panic", dps.HTTPHandlerFunc(panicHandler)) 33 | http.HandleFunc("/panic2", dps.HTTPHandlerFunc(panic2Handler)) 34 | } 35 | ` 36 | w := NewWeave() 37 | 38 | w.writeOut(os.TempDir()+"/blah", f1) 39 | 40 | aspect := Aspect{ 41 | advize: Advice{ 42 | around: "http.HandleFunc(d, dps.HTTPHandlerFunc(s))", 43 | }, 44 | pointkut: Pointcut{ 45 | def: "http.HandleFunc(d, s)", 46 | }, 47 | } 48 | 49 | aspects := []Aspect{} 50 | aspects = append(aspects, aspect) 51 | w.aspects = aspects 52 | 53 | after := w.applyAroundAdvice(os.TempDir() + "/blah") 54 | 55 | if after != expected { 56 | t.Error(after) 57 | t.Error(expected) 58 | t.Error("applyAroundAdvice is not transforming correctly") 59 | } 60 | 61 | } 62 | 63 | func TestGoTx(t *testing.T) { 64 | w := &Weave{} 65 | 66 | f1 := `package main 67 | 68 | import ( 69 | "fmt" 70 | "time" 71 | ) 72 | 73 | func stuff() { 74 | panic("panic") 75 | } 76 | 77 | func blah() { 78 | stuff() 79 | fmt.Println("never get here") 80 | } 81 | 82 | func main() { 83 | go blah() 84 | 85 | go func() { 86 | fmt.Println("inline") 87 | blah() 88 | }() 89 | 90 | time.Sleep(1 * time.Second) 91 | }` 92 | 93 | w.writeOut(os.TempDir()+"/blah_test_go", f1) 94 | 95 | aspect2 := Aspect{ 96 | advize: Advice{ 97 | before: "fmt.Println(\"there is no need to panic\")", 98 | }, 99 | pointkut: Pointcut{ 100 | def: "go", 101 | }, 102 | } 103 | 104 | aspects := []Aspect{} 105 | 106 | aspects = append(aspects, aspect2) 107 | 108 | w.aspects = aspects 109 | 110 | rootpkg := w.rootPkg() 111 | 112 | after, _ := w.processGoRoutines(os.TempDir()+"/blah_test_go", rootpkg) 113 | 114 | expected := 115 | `package main 116 | 117 | import ( 118 | "fmt" 119 | "time" 120 | ) 121 | 122 | func stuff() { 123 | panic("panic") 124 | 125 | } 126 | 127 | func blah() { 128 | stuff() 129 | fmt.Println("never get here") 130 | 131 | } 132 | 133 | func main() { 134 | go func(){ 135 | fmt.Println("there is no need to panic") 136 | blah() 137 | }() 138 | 139 | go func(){ 140 | fmt.Println("there is no need to panic") 141 | fmt.Println("inline") 142 | blah() 143 | 144 | }() 145 | 146 | time.Sleep(1 * time.Second) 147 | 148 | } 149 | ` 150 | if after != expected { 151 | t.Error("\n" + "#" + after + "#") 152 | t.Error("\n" + "#" + expected + "#") 153 | t.Error("processGoRoutines is not transforming correctly") 154 | } 155 | 156 | } 157 | 158 | func TestApplyExecutionJP(t *testing.T) { 159 | 160 | f1 := `package main 161 | 162 | import ( 163 | "fmt" 164 | "net/http" 165 | ) 166 | 167 | func hiHandler(w http.ResponseWriter, r *http.Request) { 168 | fmt.Fprintf(w, "Hi") 169 | } 170 | 171 | func hi2Handler(w http.ResponseWriter, r *http.Request) { 172 | fmt.Fprintf(w, "Hi2") 173 | } 174 | 175 | func main() { 176 | http.HandleFunc("/hi", hiHandler) 177 | http.HandleFunc("/hi2", hi2Handler) 178 | 179 | http.ListenAndServe(":3000", nil) 180 | }` 181 | 182 | expected := `package main 183 | 184 | import ( 185 | "fmt" 186 | "net/http" 187 | ) 188 | 189 | func hiHandler(w http.ResponseWriter, r *http.Request) { 190 | fmt.Println("before call") 191 | fmt.Fprintf(w, "Hi") 192 | fmt.Println("after call") 193 | } 194 | 195 | func hi2Handler(w http.ResponseWriter, r *http.Request) { 196 | fmt.Println("before call") 197 | fmt.Fprintf(w, "Hi2") 198 | fmt.Println("after call") 199 | } 200 | 201 | func main() { 202 | http.HandleFunc("/hi", hiHandler) 203 | http.HandleFunc("/hi2", hi2Handler) 204 | 205 | http.ListenAndServe(":3000", nil) 206 | } 207 | ` 208 | 209 | w := &Weave{} 210 | 211 | w.writeOut(os.TempDir()+"/blah", f1) 212 | 213 | aspect := Aspect{ 214 | advize: Advice{ 215 | before: "fmt.Println(\"before call\")", 216 | after: "fmt.Println(\"after call\")", 217 | }, 218 | pointkut: Pointcut{ 219 | def: "(http.ResponseWriter, *http.Request)", 220 | kind: 2, 221 | }, 222 | } 223 | 224 | aspects := []Aspect{} 225 | aspects = append(aspects, aspect) 226 | w.aspects = aspects 227 | 228 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 229 | 230 | if after != expected { 231 | t.Error(after) 232 | t.Error(expected) 233 | t.Error("applyExecutionJP is not transforming correctly") 234 | } 235 | 236 | } 237 | 238 | func TestContainArgs(t *testing.T) { 239 | 240 | fields := []*ast.Field{} 241 | 242 | if containArgs("main()", fields) != true { 243 | t.Error("picking up args where there should not be") 244 | } 245 | } 246 | 247 | func TestApplyExecutionJPMain(t *testing.T) { 248 | f1 := `package main 249 | 250 | import ( 251 | "fmt" 252 | ) 253 | 254 | func main() { 255 | fmt.Println("test of the microphone") 256 | }` 257 | 258 | expected := `package main 259 | 260 | import ( 261 | "fmt" 262 | ) 263 | 264 | func main() { 265 | fmt.Println("before main") 266 | fmt.Println("test of the microphone") 267 | } 268 | ` 269 | 270 | w := &Weave{} 271 | 272 | w.writeOut(os.TempDir()+"/blah", f1) 273 | 274 | aspect := Aspect{ 275 | advize: Advice{ 276 | before: "fmt.Println(\"before main\")", 277 | }, 278 | pointkut: Pointcut{ 279 | def: "main()", 280 | kind: 2, 281 | }, 282 | } 283 | 284 | aspects := []Aspect{} 285 | aspects = append(aspects, aspect) 286 | w.aspects = aspects 287 | 288 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 289 | 290 | if after != expected { 291 | t.Error(after) 292 | t.Error(expected) 293 | t.Error("applyExecutionJP is not transforming correctly") 294 | } 295 | 296 | } 297 | 298 | func TestApplyExecutionJPBefore(t *testing.T) { 299 | f1 := `package main 300 | 301 | import ( 302 | "fmt" 303 | ) 304 | 305 | func beforeBob() { 306 | fmt.Println("bob") 307 | } 308 | 309 | func main() { 310 | beforeBob() 311 | }` 312 | 313 | expected := `package main 314 | 315 | import ( 316 | "fmt" 317 | ) 318 | 319 | func beforeBob() { 320 | fmt.Println("before bob") 321 | fmt.Println("bob") 322 | } 323 | 324 | func main() { 325 | beforeBob() 326 | } 327 | ` 328 | 329 | w := &Weave{} 330 | 331 | w.writeOut(os.TempDir()+"/blah", f1) 332 | 333 | aspect := Aspect{ 334 | advize: Advice{ 335 | before: "fmt.Println(\"before bob\")", 336 | }, 337 | pointkut: Pointcut{ 338 | def: "beforeBob()", 339 | kind: 2, 340 | }, 341 | } 342 | 343 | aspects := []Aspect{} 344 | aspects = append(aspects, aspect) 345 | w.aspects = aspects 346 | 347 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 348 | 349 | if after != expected { 350 | t.Error(after) 351 | t.Error(expected) 352 | t.Error("applyExecutionJP is not transforming correctly") 353 | } 354 | 355 | } 356 | 357 | func TestApplyExecutionJPAfter(t *testing.T) { 358 | f1 := `package main 359 | 360 | import ( 361 | "fmt" 362 | ) 363 | 364 | func afterAnny() { 365 | fmt.Println("anny") 366 | } 367 | 368 | func main() { 369 | afterAnny() 370 | }` 371 | 372 | expected := `package main 373 | 374 | import ( 375 | "fmt" 376 | ) 377 | 378 | func afterAnny() { 379 | fmt.Println("anny") 380 | fmt.Println("after anny") 381 | } 382 | 383 | func main() { 384 | afterAnny() 385 | } 386 | ` 387 | 388 | w := &Weave{} 389 | 390 | w.writeOut(os.TempDir()+"/blah", f1) 391 | 392 | aspect := Aspect{ 393 | advize: Advice{ 394 | after: "fmt.Println(\"after anny\")", 395 | }, 396 | pointkut: Pointcut{ 397 | def: "afterAnny()", 398 | kind: 2, 399 | }, 400 | } 401 | 402 | aspects := []Aspect{} 403 | aspects = append(aspects, aspect) 404 | w.aspects = aspects 405 | 406 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 407 | 408 | if after != expected { 409 | t.Error(after) 410 | t.Error(expected) 411 | t.Error("applyExecutionJP is not transforming correctly") 412 | } 413 | 414 | } 415 | 416 | func TestApplyExecutionJPAfterwReturn(t *testing.T) { 417 | f1 := `package main 418 | 419 | import ( 420 | "fmt" 421 | ) 422 | 423 | func afterwReturn() int { 424 | fmt.Println("anny") 425 | return 1 426 | } 427 | 428 | func main() { 429 | afterAnny() 430 | }` 431 | 432 | expected := `package main 433 | 434 | import ( 435 | "fmt" 436 | ) 437 | 438 | func afterwReturn() int { 439 | fmt.Println("anny") 440 | fmt.Println("after anny") 441 | return 1 442 | } 443 | 444 | func main() { 445 | afterAnny() 446 | } 447 | ` 448 | 449 | w := &Weave{} 450 | 451 | w.writeOut(os.TempDir()+"/blah", f1) 452 | 453 | aspect := Aspect{ 454 | advize: Advice{ 455 | after: "fmt.Println(\"after anny\")", 456 | }, 457 | pointkut: Pointcut{ 458 | def: "afterwReturn()", 459 | kind: 2, 460 | }, 461 | } 462 | 463 | aspects := []Aspect{} 464 | aspects = append(aspects, aspect) 465 | w.aspects = aspects 466 | 467 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 468 | 469 | if after != expected { 470 | t.Error("##" + after + "##") 471 | t.Error("##" + expected + "##") 472 | t.Error("applyExecutionJP is not transforming correctly") 473 | } 474 | 475 | } 476 | 477 | func TestApplyExecutionJPAround(t *testing.T) { 478 | f1 := `package main 479 | 480 | import ( 481 | "fmt" 482 | ) 483 | 484 | func aroundArnie() { 485 | fmt.Println("arnie") 486 | } 487 | 488 | func main() { 489 | aroundArnie() 490 | }` 491 | 492 | expected := `package main 493 | 494 | import ( 495 | "fmt" 496 | ) 497 | 498 | func aroundArnie() { 499 | fmt.Println("before arnie") 500 | fmt.Println("arnie") 501 | fmt.Println("after arnie") 502 | } 503 | 504 | func main() { 505 | aroundArnie() 506 | } 507 | ` 508 | 509 | w := &Weave{} 510 | 511 | w.writeOut(os.TempDir()+"/blah", f1) 512 | 513 | aspect := Aspect{ 514 | advize: Advice{ 515 | before: "fmt.Println(\"before arnie\")", 516 | after: "fmt.Println(\"after arnie\")", 517 | }, 518 | pointkut: Pointcut{ 519 | def: "aroundArnie()", 520 | kind: 2, 521 | }, 522 | } 523 | 524 | aspects := []Aspect{} 525 | aspects = append(aspects, aspect) 526 | w.aspects = aspects 527 | 528 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 529 | 530 | if after != expected { 531 | t.Error(after) 532 | t.Error(expected) 533 | t.Error("applyExecutionJP is not transforming correctly") 534 | } 535 | 536 | } 537 | 538 | func TestApplyExecutionJPInnerFors(t *testing.T) { 539 | f1 := `package main 540 | 541 | import ( 542 | "fmt" 543 | ) 544 | 545 | func innerFors() { 546 | fmt.Println("inner") 547 | } 548 | 549 | func main() { 550 | innerFors() 551 | }` 552 | 553 | expected := `package main 554 | 555 | import ( 556 | "fmt" 557 | ) 558 | 559 | func innerFors() { 560 | for i:=0; i<10; i++ { 561 | fmt.Println(i) 562 | } 563 | fmt.Println("inner") 564 | } 565 | 566 | func main() { 567 | innerFors() 568 | } 569 | ` 570 | 571 | w := &Weave{} 572 | 573 | w.writeOut(os.TempDir()+"/blah", f1) 574 | 575 | aspect := Aspect{ 576 | advize: Advice{ 577 | before: "for i:=0; i<10; i++ {\n\tfmt.Println(i)\n}", 578 | }, 579 | pointkut: Pointcut{ 580 | def: "innerFors()", 581 | kind: 2, 582 | }, 583 | } 584 | 585 | aspects := []Aspect{} 586 | aspects = append(aspects, aspect) 587 | w.aspects = aspects 588 | 589 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 590 | 591 | if after != expected { 592 | t.Error(after) 593 | t.Error(expected) 594 | t.Error("applyExecutionJP is not transforming correctly") 595 | } 596 | 597 | } 598 | 599 | func TestApplyExecutionJPRetStr(t *testing.T) { 600 | f1 := `package main 601 | 602 | import ( 603 | "fmt" 604 | ) 605 | 606 | func retStr() { 607 | fmt.Println("string") 608 | } 609 | 610 | func main() { 611 | retStr() 612 | }` 613 | 614 | expected := `package main 615 | 616 | import ( 617 | "fmt" 618 | ) 619 | 620 | func retStr() { 621 | fmt.Println("before ret") 622 | fmt.Println("string") 623 | } 624 | 625 | func main() { 626 | retStr() 627 | } 628 | ` 629 | 630 | w := &Weave{} 631 | 632 | w.writeOut(os.TempDir()+"/blah", f1) 633 | 634 | aspect := Aspect{ 635 | advize: Advice{ 636 | before: "fmt.Println(\"before ret\")", 637 | }, 638 | pointkut: Pointcut{ 639 | def: "retStr()", 640 | kind: 2, 641 | }, 642 | } 643 | 644 | aspects := []Aspect{} 645 | aspects = append(aspects, aspect) 646 | w.aspects = aspects 647 | 648 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 649 | 650 | if after != expected { 651 | t.Error(after) 652 | t.Error(expected) 653 | t.Error("applyExecutionJP is not transforming correctly") 654 | } 655 | 656 | } 657 | 658 | func TestApplyExecutionJPRetBool(t *testing.T) { 659 | f1 := `package main 660 | 661 | import ( 662 | "fmt" 663 | ) 664 | 665 | func retBool() bool { 666 | if 1 == 1 { 667 | return true 668 | } else { 669 | return false 670 | } 671 | } 672 | 673 | func main() { 674 | retBool() 675 | }` 676 | 677 | expected := `package main 678 | 679 | import ( 680 | "fmt" 681 | ) 682 | 683 | func retBool() bool { 684 | fmt.Println("before bool") 685 | if 1 == 1 { 686 | return true 687 | } else { 688 | return false 689 | } 690 | } 691 | 692 | func main() { 693 | retBool() 694 | } 695 | ` 696 | 697 | w := &Weave{} 698 | 699 | w.writeOut(os.TempDir()+"/blah", f1) 700 | 701 | aspect := Aspect{ 702 | advize: Advice{ 703 | before: "fmt.Println(\"before bool\")", 704 | }, 705 | pointkut: Pointcut{ 706 | def: "retBool()", 707 | kind: 2, 708 | }, 709 | } 710 | 711 | aspects := []Aspect{} 712 | aspects = append(aspects, aspect) 713 | w.aspects = aspects 714 | 715 | after := w.applyExecutionJP(os.TempDir()+"/blah", f1) 716 | 717 | if after != expected { 718 | t.Error(after) 719 | t.Error(expected) 720 | t.Error("applyExecutionJP is not transforming correctly") 721 | } 722 | 723 | } 724 | 725 | func TestApplyCallAdvice(t *testing.T) { 726 | f1 := `package main 727 | 728 | import ( 729 | "fmt" 730 | ) 731 | 732 | func getStuff(i int) { 733 | fmt.Println(i) 734 | } 735 | 736 | func main() { 737 | for i:=0; i<10; i++ { 738 | getStuff(i) 739 | } 740 | }` 741 | 742 | expected := `package main 743 | 744 | import ( 745 | "fmt" 746 | ) 747 | 748 | func getStuff(i int) { 749 | fmt.Println(i) 750 | } 751 | 752 | func main() { 753 | for i:=0; i<10; i++ { 754 | fmt.Println("before call") 755 | getStuff(i) 756 | } 757 | } 758 | ` 759 | 760 | w := &Weave{} 761 | 762 | w.writeOut(os.TempDir()+"/blah", f1) 763 | 764 | aspect := Aspect{ 765 | advize: Advice{ 766 | before: "fmt.Println(\"before call\")", 767 | }, 768 | pointkut: Pointcut{ 769 | def: "getStuff(int i)", 770 | kind: 1, 771 | }, 772 | } 773 | 774 | aspects := []Aspect{} 775 | aspects = append(aspects, aspect) 776 | w.aspects = aspects 777 | 778 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 779 | 780 | if after != expected { 781 | t.Error(after) 782 | t.Error(expected) 783 | t.Error("applyCallAdvice is not transforming correctly") 784 | } 785 | 786 | } 787 | 788 | func TestApplyCallAdvicePkgDot(t *testing.T) { 789 | 790 | f1 := `package main 791 | 792 | import ( 793 | "fmt" 794 | "strconv" 795 | ) 796 | 797 | func blah() { 798 | fmt.Println(strconv.Itoa(2)) 799 | } 800 | 801 | func main() { 802 | fmt.Println(strconv.Itoa(44)) 803 | 804 | blah() 805 | }` 806 | 807 | expected := `package main 808 | 809 | import ( 810 | "fmt" 811 | "strconv" 812 | ) 813 | 814 | func blah() { 815 | fmt.Println("strconv called") 816 | fmt.Println(strconv.Itoa(2)) 817 | } 818 | 819 | func main() { 820 | fmt.Println("strconv called") 821 | fmt.Println(strconv.Itoa(44)) 822 | 823 | blah() 824 | } 825 | ` 826 | 827 | w := &Weave{} 828 | 829 | w.writeOut(os.TempDir()+"/blah", f1) 830 | 831 | aspect := Aspect{ 832 | advize: Advice{ 833 | before: "fmt.Println(\"strconv called\")", 834 | }, 835 | pointkut: Pointcut{ 836 | def: "strconv.Itoa(int i)", 837 | kind: 1, 838 | }, 839 | } 840 | 841 | aspects := []Aspect{} 842 | aspects = append(aspects, aspect) 843 | w.aspects = aspects 844 | 845 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 846 | 847 | if after != expected { 848 | t.Error(after) 849 | t.Error(expected) 850 | t.Error("applyCallAdvice is not transforming correctly") 851 | } 852 | 853 | } 854 | 855 | func TestPkgRewrite(t *testing.T) { 856 | f1 := `package main 857 | 858 | import ( 859 | "github.com/some/stuff/subpkg" 860 | ) 861 | 862 | func main() { 863 | }` 864 | 865 | s := "github.com/some/stuff" 866 | 867 | w := NewWeave() 868 | w.writeOut(os.TempDir()+"/blah", f1) 869 | 870 | af := w.ParseAST(os.TempDir() + "/blah") 871 | 872 | pruned := w.pruneImports(af, s) 873 | 874 | if pruned[0].Path.Value != "\"_weave/github.com/some/stuff/subpkg\"" { 875 | t.Error(pruned[0].Path.Value) 876 | t.Error("pruneImports not working") 877 | } 878 | 879 | } 880 | 881 | func TestApplyGlobalAdvice(t *testing.T) { 882 | 883 | f1 := `package main 884 | 885 | import ( 886 | "fmt" 887 | ) 888 | 889 | func getStuff(i int) { 890 | fmt.Println(i) 891 | } 892 | 893 | func main() { 894 | 895 | for i := 0; i < 10; i++ { 896 | getStuff(i) 897 | } 898 | }` 899 | 900 | expected := `package main 901 | 902 | import ( 903 | "fmt" 904 | ) 905 | var myCnt int =0 906 | 907 | func getStuff(i int) { 908 | myCnt += 1 909 | fmt.Println(i) 910 | } 911 | 912 | func main() { 913 | 914 | for i := 0; i < 10; i++ { 915 | getStuff(i) 916 | } 917 | fmt.Println(myCnt) 918 | } 919 | ` 920 | 921 | w := NewWeave() 922 | 923 | w.writeOut(os.TempDir()+"/blah", f1) 924 | 925 | aspect := Aspect{ 926 | advize: Advice{ 927 | before: "myCnt += 1", 928 | }, 929 | pointkut: Pointcut{ 930 | def: "getStuff(int)", 931 | kind: 2, 932 | }, 933 | } 934 | 935 | aspect2 := Aspect{ 936 | advize: Advice{ 937 | before: "var myCnt int =0", 938 | }, 939 | pointkut: Pointcut{ 940 | def: "*", 941 | kind: 2, 942 | }, 943 | } 944 | 945 | aspect3 := Aspect{ 946 | advize: Advice{ 947 | after: "fmt.Println(myCnt)", 948 | }, 949 | pointkut: Pointcut{ 950 | def: "main()", 951 | kind: 2, 952 | }, 953 | } 954 | 955 | aspects := []Aspect{} 956 | aspects = append(aspects, aspect) 957 | aspects = append(aspects, aspect2) 958 | aspects = append(aspects, aspect3) 959 | 960 | w.aspects = aspects 961 | 962 | fp := os.TempDir() + "/blah" 963 | stuff := w.applyGlobalAdvice(fp, f1) 964 | w.writeOut(fp, stuff) 965 | 966 | stuff = w.applyCallAdvice(fp, stuff) 967 | w.writeOut(fp, stuff) 968 | 969 | after := w.applyExecutionJP(fp, stuff) 970 | w.writeOut(fp, stuff) 971 | 972 | if after != expected { 973 | t.Error(after) 974 | t.Error(expected) 975 | t.Error("applyGlobalAdvice is not transforming correctly") 976 | } 977 | 978 | } 979 | 980 | func TestApplyCallAsArg(t *testing.T) { 981 | 982 | f1 := `package main 983 | 984 | import ( 985 | "fmt" 986 | "strconv" 987 | ) 988 | 989 | func main() { 990 | fmt.Println("some stuff" + strconv.Itoa(2)) + 991 | "and some other stuff" + strconv.Itoa(42)) 992 | }` 993 | 994 | expected := `package main 995 | 996 | import ( 997 | "fmt" 998 | "strconv" 999 | ) 1000 | 1001 | func main() { 1002 | fmt.Println("strconv called") 1003 | fmt.Println("strconv called") 1004 | fmt.Println("some stuff" + strconv.Itoa(2)) + 1005 | "and some other stuff" + strconv.Itoa(42)) 1006 | } 1007 | ` 1008 | 1009 | w := NewWeave() 1010 | 1011 | w.writeOut(os.TempDir()+"/blah", f1) 1012 | 1013 | aspect := Aspect{ 1014 | advize: Advice{ 1015 | before: "fmt.Println(\"strconv called\")", 1016 | }, 1017 | pointkut: Pointcut{ 1018 | def: "strconv.Itoa(int i)", 1019 | kind: 1, 1020 | }, 1021 | } 1022 | 1023 | aspects := []Aspect{} 1024 | aspects = append(aspects, aspect) 1025 | w.aspects = aspects 1026 | 1027 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1028 | 1029 | if after != expected { 1030 | t.Error(after) 1031 | t.Error(expected) 1032 | t.Error("applyCallAdvice is not transforming correctly") 1033 | } 1034 | 1035 | } 1036 | 1037 | func TestApplyCallAsArgComma(t *testing.T) { 1038 | 1039 | f1 := `package main 1040 | 1041 | import ( 1042 | "fmt" 1043 | "strconv" 1044 | ) 1045 | 1046 | func main() { 1047 | fmt.Printf("some stuff %d", 1048 | strconv.Itoa(2)) 1049 | }` 1050 | 1051 | expected := `package main 1052 | 1053 | import ( 1054 | "fmt" 1055 | "strconv" 1056 | ) 1057 | 1058 | func main() { 1059 | fmt.Println("strconv called") 1060 | fmt.Printf("some stuff %d", 1061 | strconv.Itoa(2)) 1062 | } 1063 | ` 1064 | 1065 | w := NewWeave() 1066 | 1067 | w.writeOut(os.TempDir()+"/blah", f1) 1068 | 1069 | aspect := Aspect{ 1070 | advize: Advice{ 1071 | before: "fmt.Println(\"strconv called\")", 1072 | }, 1073 | pointkut: Pointcut{ 1074 | def: "strconv.Itoa(int i)", 1075 | kind: 1, 1076 | }, 1077 | } 1078 | 1079 | aspects := []Aspect{} 1080 | aspects = append(aspects, aspect) 1081 | w.aspects = aspects 1082 | 1083 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1084 | 1085 | if after != expected { 1086 | t.Error(after) 1087 | t.Error(expected) 1088 | t.Error("applyCallAdvice is not transforming correctly") 1089 | } 1090 | 1091 | } 1092 | 1093 | func TestApplyCallAsArgComma2(t *testing.T) { 1094 | 1095 | f1 := `package main 1096 | 1097 | import ( 1098 | "fmt" 1099 | "strconv" 1100 | ) 1101 | 1102 | func main() { 1103 | err := db.QueryRow("select blah from users where id = $1 limit 1", 1104 | strconv.Itoa(uid)).Scan(&blah) 1105 | }` 1106 | 1107 | expected := `package main 1108 | 1109 | import ( 1110 | "fmt" 1111 | "strconv" 1112 | ) 1113 | 1114 | func main() { 1115 | fmt.Println("strconv called") 1116 | err := db.QueryRow("select blah from users where id = $1 limit 1", 1117 | strconv.Itoa(uid)).Scan(&blah) 1118 | } 1119 | ` 1120 | 1121 | w := NewWeave() 1122 | 1123 | w.writeOut(os.TempDir()+"/blah", f1) 1124 | 1125 | aspect := Aspect{ 1126 | advize: Advice{ 1127 | before: "fmt.Println(\"strconv called\")", 1128 | }, 1129 | pointkut: Pointcut{ 1130 | def: "strconv.Itoa(int i)", 1131 | kind: 1, 1132 | }, 1133 | } 1134 | 1135 | aspects := []Aspect{} 1136 | aspects = append(aspects, aspect) 1137 | w.aspects = aspects 1138 | 1139 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1140 | 1141 | if after != expected { 1142 | t.Error(after) 1143 | t.Error(expected) 1144 | t.Error("applyCallAdvice is not transforming correctly") 1145 | } 1146 | 1147 | } 1148 | 1149 | func TestCallInCompositeLit(t *testing.T) { 1150 | 1151 | f1 := `package main 1152 | 1153 | import ( 1154 | "fmt" 1155 | "strconv" 1156 | ) 1157 | 1158 | func main() { 1159 | value := map[string]string{ 1160 | "name": userName, 1161 | "id": strconv.Itoa(1), 1162 | "id2": strconv.Itoa(int(2)), 1163 | "id3": strconv.Itoa(int(3)), 1164 | } 1165 | }` 1166 | 1167 | expected := `package main 1168 | 1169 | import ( 1170 | "fmt" 1171 | "strconv" 1172 | ) 1173 | 1174 | func main() { 1175 | fmt.Println("strconv called") 1176 | fmt.Println("strconv called") 1177 | fmt.Println("strconv called") 1178 | value := map[string]string{ 1179 | "name": userName, 1180 | "id": strconv.Itoa(1), 1181 | "id2": strconv.Itoa(int(2)), 1182 | "id3": strconv.Itoa(int(3)), 1183 | } 1184 | } 1185 | ` 1186 | 1187 | w := NewWeave() 1188 | 1189 | w.writeOut(os.TempDir()+"/blah", f1) 1190 | 1191 | aspect := Aspect{ 1192 | advize: Advice{ 1193 | before: "fmt.Println(\"strconv called\")", 1194 | }, 1195 | pointkut: Pointcut{ 1196 | def: "strconv.Itoa(int i)", 1197 | kind: 1, 1198 | }, 1199 | } 1200 | 1201 | aspects := []Aspect{} 1202 | aspects = append(aspects, aspect) 1203 | w.aspects = aspects 1204 | 1205 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1206 | 1207 | if after != expected { 1208 | t.Error(printWLines(after)) 1209 | t.Error(printWLines(expected)) 1210 | t.Error("applyCallAdvice is not transforming correctly") 1211 | } 1212 | 1213 | } 1214 | 1215 | func TestCallBlah(t *testing.T) { 1216 | 1217 | f1 := `package main 1218 | 1219 | import ( 1220 | "fmt" 1221 | "strconv" 1222 | ) 1223 | 1224 | func main() { 1225 | err := db.QueryRow("select blah1, blah2 from blahs where id = $1 limit 1", 1226 | agent_id).Scan(&tRes.Id, &tRes.Blah) 1227 | }` 1228 | 1229 | expected := `package main 1230 | 1231 | import ( 1232 | "fmt" 1233 | "strconv" 1234 | ) 1235 | 1236 | func main() { 1237 | startTime := time.Now() 1238 | err := db.QueryRow("select blah1, blah2 from blahs where id = $1 limit 1", 1239 | agent_id).Scan(&tRes.Id, &tRes.Blah) 1240 | endTime := time.Now() 1241 | t := int(((endTime.Sub(startTime)).Nanoseconds() / 1000000)) 1242 | fmt.Println("query took %d seconds", t) 1243 | } 1244 | ` 1245 | 1246 | w := NewWeave() 1247 | 1248 | w.writeOut(os.TempDir()+"/blah", f1) 1249 | 1250 | aspect := Aspect{ 1251 | advize: Advice{ 1252 | before: "startTime := time.Now()", 1253 | }, 1254 | pointkut: Pointcut{ 1255 | def: "QueryRow(s string)", 1256 | kind: 1, 1257 | }, 1258 | } 1259 | 1260 | aspect2 := Aspect{ 1261 | advize: Advice{ 1262 | after: "endTime := time.Now()\nt := int(((endTime.Sub(startTime)).Nanoseconds() / 1000000))\nfmt.Println(\"query took %d seconds\", t)", 1263 | }, 1264 | pointkut: Pointcut{ 1265 | def: "QueryRow(s string)", 1266 | kind: 1, 1267 | }, 1268 | } 1269 | 1270 | aspects := []Aspect{} 1271 | aspects = append(aspects, aspect) 1272 | aspects = append(aspects, aspect2) 1273 | w.aspects = aspects 1274 | 1275 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1276 | 1277 | if after != expected { 1278 | t.Error(printWLines(after)) 1279 | t.Error(printWLines(expected)) 1280 | t.Error("applyCallAdvice is not transforming correctly") 1281 | } 1282 | 1283 | } 1284 | 1285 | func TestCallBlah2(t *testing.T) { 1286 | 1287 | f1 := `package main 1288 | 1289 | import ( 1290 | "fmt" 1291 | "strconv" 1292 | ) 1293 | 1294 | func main() { 1295 | err := db.QueryRow("select blah1, blah2 from blahs where id = $1 limit 1", 1296 | agent_id).Scan(&tRes.Id, 1297 | &tRes.Blah) 1298 | }` 1299 | 1300 | expected := `package main 1301 | 1302 | import ( 1303 | "fmt" 1304 | "strconv" 1305 | ) 1306 | 1307 | func main() { 1308 | startTime := time.Now() 1309 | err := db.QueryRow("select blah1, blah2 from blahs where id = $1 limit 1", 1310 | agent_id).Scan(&tRes.Id, 1311 | &tRes.Blah) 1312 | endTime := time.Now() 1313 | t := int(((endTime.Sub(startTime)).Nanoseconds() / 1000000)) 1314 | fmt.Println("query took %d seconds", t) 1315 | } 1316 | ` 1317 | 1318 | w := NewWeave() 1319 | 1320 | w.writeOut(os.TempDir()+"/blah", f1) 1321 | 1322 | aspect := Aspect{ 1323 | advize: Advice{ 1324 | before: "startTime := time.Now()", 1325 | }, 1326 | pointkut: Pointcut{ 1327 | def: "QueryRow(s string)", 1328 | kind: 1, 1329 | }, 1330 | } 1331 | 1332 | aspect2 := Aspect{ 1333 | advize: Advice{ 1334 | after: "endTime := time.Now()\nt := int(((endTime.Sub(startTime)).Nanoseconds() / 1000000))\nfmt.Println(\"query took %d seconds\", t)", 1335 | }, 1336 | pointkut: Pointcut{ 1337 | def: "QueryRow(s string)", 1338 | kind: 1, 1339 | }, 1340 | } 1341 | 1342 | aspects := []Aspect{} 1343 | aspects = append(aspects, aspect) 1344 | aspects = append(aspects, aspect2) 1345 | w.aspects = aspects 1346 | 1347 | after := w.applyCallAdvice(os.TempDir()+"/blah", f1) 1348 | 1349 | if after != expected { 1350 | t.Error(printWLines(after)) 1351 | t.Error(printWLines(expected)) 1352 | t.Error("applyCallAdvice is not transforming correctly") 1353 | } 1354 | 1355 | } 1356 | 1357 | func printWLines(stuff string) string { 1358 | rstr := "" 1359 | 1360 | b := strings.Split(stuff, "\n") 1361 | for i := 0; i < len(b); i++ { 1362 | rstr += strconv.Itoa(i+1) + ":\t" + b[i] + "\n" 1363 | } 1364 | 1365 | return rstr 1366 | } 1367 | -------------------------------------------------------------------------------- /weave/within.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "go/ast" 5 | "go/parser" 6 | "go/token" 7 | "strings" 8 | ) 9 | 10 | // applyWithinJP applies any advice for within joinpoints 11 | // right now this expects both 'before && after' 12 | func (w *Weave) applyWithinJP(fname string, stuff string) string { 13 | 14 | rout := stuff 15 | 16 | importsNeeded := []string{} 17 | 18 | for i := 0; i < len(w.aspects); i++ { 19 | aspect := w.aspects[i] 20 | if !aspect.pointkut.isWithin() { 21 | continue 22 | } 23 | 24 | pk := aspect.pointkut.def 25 | 26 | fset := token.NewFileSet() 27 | file, err := parser.ParseFile(fset, fname, rout, parser.Mode(0)) 28 | if err != nil { 29 | w.flog.Println("Failed to parse source: %s", err.Error()) 30 | } 31 | 32 | linecnt := 0 33 | 34 | // look for function declarations - ala look for execution 35 | // joinpoints 36 | for _, decl := range file.Decls { 37 | fn, ok := decl.(*ast.FuncDecl) 38 | if !ok { 39 | continue 40 | } 41 | 42 | fpk := strings.Split(pk, "(")[0] 43 | 44 | // if function name missing --> wildcard 45 | if fpk == "" { 46 | fpk = fn.Name.Name 47 | } 48 | 49 | if fn.Name.Name == fpk && containArgs(pk, fn.Type.Params.List) { 50 | 51 | wb := WithinBlock{ 52 | name: fn.Name.Name, 53 | fname: fname, 54 | stmts: fn.Body.List, 55 | linecnt: linecnt, 56 | importsNeeded: importsNeeded, 57 | aspect: aspect, 58 | fset: fset, 59 | } 60 | 61 | rout, linecnt, importsNeeded = wb.iterateBodyStatements(w) 62 | 63 | } 64 | } 65 | 66 | } 67 | 68 | if len(importsNeeded) > 0 { 69 | // add any imports for this piece of advice 70 | rout = w.writeMissingImports(fname, rout, importsNeeded) 71 | } 72 | 73 | return rout 74 | } 75 | 76 | // WithinBlock contains all the info to perform a withinBlock pointcut 77 | type WithinBlock struct { 78 | name string 79 | fname string 80 | stmts []ast.Stmt 81 | linecnt int 82 | importsNeeded []string 83 | aspect Aspect 84 | fset *token.FileSet 85 | } 86 | 87 | // iterateBodyStatements places within advice when a joinpoint is found 88 | func (wb *WithinBlock) iterateBodyStatements(w *Weave) (string, int, []string) { 89 | rout := "" 90 | 91 | for i := 0; i < len(wb.stmts); i++ { 92 | 93 | rout = wb.insertInWithin(wb.stmts[i], w) 94 | } 95 | 96 | return rout, wb.linecnt, wb.importsNeeded 97 | } 98 | 99 | // HACK HACK HACK 100 | func grabMethodName(a ast.Stmt) string { 101 | es := a.(*ast.ExprStmt) 102 | 103 | s, ok := es.X.(*ast.CallExpr) 104 | if !ok { 105 | return "" 106 | } else { 107 | e, ok := s.Fun.(*ast.Ident) 108 | if !ok { 109 | return "" 110 | } else { 111 | return e.Name 112 | } 113 | } 114 | } 115 | 116 | // insertInWithin places before/after advice around a statement 117 | func (wb *WithinBlock) insertInWithin(a ast.Stmt, w *Weave) string { 118 | rout := "" 119 | 120 | mName := grabMethodName(a) 121 | 122 | // begin line 123 | begin := wb.fset.Position(a.Pos()).Line - 1 124 | after := wb.fset.Position(a.End()).Line + 1 125 | 126 | // until this is refactored - any lines we add in our 127 | // advice need to be accounted for w/begin 128 | before_advice := formatAdvice(wb.aspect.advize.before, mName) 129 | after_advice := formatAdvice(wb.aspect.advize.after, mName) 130 | 131 | if before_advice != "" { 132 | rout = w.writeAtLine(wb.fname, begin+wb.linecnt, before_advice) 133 | wb.linecnt += strings.Count(before_advice, "\n") + 1 134 | } 135 | 136 | if after_advice != "" { 137 | rout = w.writeAtLine(wb.fname, after+wb.linecnt-1, after_advice) 138 | 139 | wb.linecnt += strings.Count(after_advice, "\n") + 1 140 | } 141 | 142 | for t := 0; t < len(wb.aspect.importz); t++ { 143 | wb.importsNeeded = append(wb.importsNeeded, wb.aspect.importz[t]) 144 | } 145 | 146 | return rout 147 | } 148 | 149 | // formatAdvice subsitutes any reserved keywords 150 | // currently supported is mName 151 | // mName is the currently called method name 152 | func formatAdvice(advice string, mName string) string { 153 | return strings.Replace(advice, "mName", "\""+mName+"\"", -1) 154 | } 155 | -------------------------------------------------------------------------------- /weave/within_test.go: -------------------------------------------------------------------------------- 1 | package weave 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | ) 7 | 8 | func TestApplyWithinJP(t *testing.T) { 9 | f1 := `package main 10 | 11 | import ( 12 | "fmt" 13 | "time" 14 | ) 15 | 16 | func slow() { 17 | time.Sleep(1 * time.Second) 18 | } 19 | 20 | func fast() { 21 | } 22 | 23 | func everyCall() { 24 | slow() 25 | fast() 26 | slow() 27 | } 28 | 29 | func main() { 30 | everyCall() 31 | }` 32 | 33 | expected := `package main 34 | 35 | import ( 36 | "fmt" 37 | "time" 38 | ) 39 | 40 | func slow() { 41 | time.Sleep(1 * time.Second) 42 | } 43 | 44 | func fast() { 45 | } 46 | 47 | func everyCall() { 48 | ccall("slow", time.Now()) 49 | slow() 50 | ucall("slow", time.Now()) 51 | ccall("fast", time.Now()) 52 | fast() 53 | ucall("fast", time.Now()) 54 | ccall("slow", time.Now()) 55 | slow() 56 | ucall("slow", time.Now()) 57 | } 58 | 59 | func main() { 60 | everyCall() 61 | } 62 | ` 63 | 64 | w := &Weave{} 65 | 66 | w.writeOut(os.TempDir()+"/blah", f1) 67 | 68 | aspect := Aspect{ 69 | advize: Advice{ 70 | before: "ccall(mName, time.Now())", 71 | after: "ucall(mName, time.Now())", 72 | }, 73 | pointkut: Pointcut{ 74 | def: "everyCall()", 75 | kind: 3, 76 | }, 77 | } 78 | 79 | aspects := []Aspect{} 80 | aspects = append(aspects, aspect) 81 | w.aspects = aspects 82 | 83 | after := w.applyWithinJP(os.TempDir()+"/blah", f1) 84 | 85 | if after != expected { 86 | t.Error(after) 87 | t.Error(expected) 88 | t.Error("applyWithinJP is not transforming correctly") 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /wercker.yml: -------------------------------------------------------------------------------- 1 | box: google/golang 2 | # Build definition 3 | build: 4 | # The steps that will be executed on build 5 | steps: 6 | # Sets the go workspace and places you package 7 | # at the right place in the workspace tree 8 | - setup-go-workspace 9 | 10 | # Gets the dependencies 11 | - script: 12 | name: go get 13 | code: | 14 | cd $WERCKER_SOURCE_DIR 15 | go get -t ./... 16 | 17 | # Test the project 18 | - script: 19 | name: go test 20 | code: | 21 | go test ./... 22 | 23 | --------------------------------------------------------------------------------