| # Wolf Analysis | |
| The code in this directory is __experimental__ and not yet ready for public | |
| consumption. This file briefly describes its design and intended use. | |
| ## Intention | |
| Wolf analysis is intended as a broader, more extensible version of flow analysis | |
| for use in the analyzer package and the linter. If it proves useful, we may | |
| expand it to be usable by clients of the analyzer such as code generators. | |
| The reason for making a new type of analysis, rather than extending flow | |
| analysis, is because the design of flow analysis is heavily constrained in ways | |
| that make it expensive to modify, maintain, and extend: | |
| - It has to operate in parallel with type inference. This means that it can only | |
| make a single pass through the source code, and any time it "looks ahead", the | |
| code it is looking at doesn't yet have types. This forces it to make some | |
| conservative assumptions when analyzing loops and closures--for example, on | |
| entry to a loop, flow analysis knows which variables are written inside the | |
| loop body, but it doesn't know the types of the values that are written to | |
| those variables; so it has to discard type promotions for all of them. If | |
| later analysis shows that the values that were written are compatible with the | |
| promoted type, it's too late to go back and update the analysis. | |
| - It can't be improved without bumping the language version (or it will break | |
| existing programs). | |
| - All of its analyses must be 100% sound, because they are relied on by later | |
| compilation stages. This severly limits its ability to account for human | |
| intent, which we would sometimes like to do in lints. | |
| - It has to be shared between the analyzer and the common front end, which means | |
| that all of the data structures it acts on must be abstract. | |
| Additionally, flow analysis doesn't understand special behavioral guarantees of | |
| methods and types declared outside the SDK, and it never looks outside of a | |
| single method body. | |
| ## Design | |
| The design consists of multiple analysis stages: | |
| - AST-to-IR. The analyzer's AST is converted to a stack-based IR (intermediate | |
| representation). The proposed IR and the approach for converting to it are | |
| described in https://flutter.dev/go/dart-static-analysis-ir. | |
| - Scope analysis. A pass is made through the IR, identifying all the state | |
| variables of interest. This includes local variables as well as any other | |
| pieces of state that are needed for whatever lints are enabled. This stage | |
| records the set of state variables that could potentially be changed inside | |
| each loop, block, closure, etc., for use in later analysis stages. | |
| - SSA (single static assignment) analysis. A pass is made through the IR to | |
| assign SSA node ids to each state variable and stack value. Queries are | |
| generated at points that are of interest to whatever lints are enabled (e.g. a | |
| lint that verifies that certain kinds of values are never null will generate | |
| queries that ask "is the value represented by this SSA node null?"). | |
| - Query solver. Each query generated by SSA analysis is resolved by working | |
| backwards through the IR, rewriting it in terms of previous SSA nodes, until a | |
| point in the code is reached where the query either can or can't be | |
| definitively answered. The mechanism for rewriting queries is inspired by | |
| Prolog. Depending on the result of the query, a lint output might be | |
| generated. | |
| Other analysis stages might be added in the future. For example, if we find that | |
| some lints can't be easily expressed in terms of queries, but can still benefit | |
| from the SSA representation, we may add other analysis stages after SSA | |
| analysis, to support those lints. | |
| ## Support structure | |
| In addition to the pieces above, there is a simple interpreter that executes the | |
| IR. The interpreter is not intended for production use; it is just sophisticated | |
| enough to serve as the basis for unit testing the other stages. For example, we | |
| use it in the unit tests of the AST-to-IR conversion stage, to make sure that | |
| the output of this stage properly reflects Dart semantics. (If, instead, our | |
| unit tests simply compared the output of AST-to-IR conversion to an expected | |
| sequence of instructions, the unit tests would be much more brittle, and there | |
| would be a much greater risk of human error in reviewing them.) |