Difference between revisions of "Using the Atlas Shell"
(Update for v3.6.0) |
|||
(82 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
− | + | The easiest way to get started with Atlas is [http://www.ensoftcorp.com/atlas/developers/#smartview Smart Views], but sometimes you need a different kind of graph, statistics, or even your own custom tooling. That's where the Atlas Shell comes in. The shell allows you to execute commands interactively using our Scala-based scripting language or to write scripts using Java, Scala, or any other JVM compatible language. | |
− | + | ||
− | + | ||
− | + | {{#ev:vimeo|110313322}} | |
− | + | ==<div id="ConfiguringShellProject">Configuring a Shell Project</div>== | |
− | + | The Atlas Shell requires a Scala project in the workspace for context (mostly for establishing the classpath). This project can also be used to build up longer scripts and utilities, which may be written in either Java, Scala, or any mix of JVM compatible languages. | |
− | + | ||
− | + | To configure the shell project please follow the instructions in the video below. You can download the zip file referenced in the video here: [http://cdn.ensoftcorp.com/atlas_docs/example-projects/Example-Shell-Plug-in.zip Example-Shell-Plug-in.zip]. | |
+ | |||
+ | {{#ev:vimeo|75989910}} | ||
+ | |||
+ | ===Importing a Scala Project Template=== | ||
+ | For convenience, a sample Scala project is supplied in this [http://cdn.ensoftcorp.com/atlas_docs/example-projects/Example-Shell-Plug-in.zip Example Shell zip file]. You can import the project from the zip file using the steps below. | ||
+ | |||
+ | *Menu: <code>File -> Import...</code> | ||
+ | *Select "Existing Projects into Workspace" under "General" | ||
+ | *Use "Select archive file" and pick the zip file above. | ||
+ | *Select Finish. | ||
+ | |||
+ | If you would prefer to create the Scala project manually, refer to the section [[#create-scala-project|Manually Creating a Scala Project]] below. | ||
+ | |||
+ | ===<div id="create-scala-project">Manually Creating a Scala Project for use with the Shell</div>=== | ||
+ | Create a Plug-in project, then convert it into a Scala project. | ||
+ | |||
+ | Then, to set the classpath for use with Atlas, open the manifest and add as dependencies: | ||
+ | |||
+ | *the plugins included with the Scala IDE (org.scala*, scalariform) | ||
+ | *the Atlas plugins | ||
+ | **<code>com.ensoftcorp.atlas</code> | ||
+ | **<code>com.ensoftcorp.atlas.db</code> | ||
+ | **<code>com.ensoftcorp.atlas.db.common</code> | ||
+ | **<code>com.ensoftcorp.atlas.db.implementation</code> | ||
+ | **<code>com.ensoftcorp.atlas.core</code> | ||
+ | **<code>com.ensoftcorp.atlas.ui</code> | ||
+ | **<code>com.ensoftcorp.atlas.ui.shell</code> | ||
+ | |||
+ | Finally, disable indexing of the Scala project from the menu item <code>Atlas -> Manage Project Settings</code>. | ||
+ | |||
+ | =<div id="executing">Executing Commands Interactively</div>= | ||
+ | Once you have configured a shell project right click on the project and select the menu item: <code>Atlas -> Open Shell</code>. The shell will open and automatically index your project if the Atlas Smart View has not done so already. | ||
+ | |||
+ | [[File:Atlas_Shell.png|518px|Screenshot of the Atlas Shell]] | ||
+ | |||
+ | Let's try a simple command. Type in the command below and then press enter. Now you know how many private fields are in your indexed projects. | ||
+ | |||
+ | <code>index.nodesTaggedWithAll(XCSG.Field,XCSG.privateVisibility).eval.nodes.size</code> | ||
+ | |||
+ | To create custom graphs, scripts, and much more follow the [[#QueryLanguageTutorial|Query Language Tutorial]] below. | ||
+ | |||
+ | ==<div id="InformationQuery">What kind of information can I query?</div>== | ||
+ | Atlas indexes source code in the workspace, and produces an index which is essentially a graph. The entire graph is usually referred to as the "index". The Atlas query language is an internal DSL embedded within Java. The primary interface used to build queries is <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>. Queries written using <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code> are evaluated, yielding a <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/db/graph/Graph.html Graph]</code>, which is a subset of the index. One may write queries entirely in terms of <code>Graph</code>, but most routine queries are easier to write using <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
The index currently contains: | The index currently contains: | ||
− | + | *The major declarations (projects, types, packages, fields, methods), and associated relationships, such as the type hierarchy. | |
− | + | *A "summary" graph, which includes method and control-flow-granularity relationships, such as calls and reads/writes of fields, among other things. | |
− | + | *Control flow. | |
− | < | + | *Data flow. |
− | < | + | |
− | </ | + | Whether using <code>Graph</code> or <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>, most queries will involve use of the constants defined in [http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html XCSG], which essentially forms the schema for the index. Studying the javadocs for this interface is highly recommended. In addition to the above, the [http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/script/Common.html Common] class provides a handful of convenience methods for writing queries. In particular, <code>Common.index()</code> is the starting point for a query based on <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>. |
+ | |||
+ | Additional documentation about the query language, graph data structures and more can be found in the [http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/index.html Atlas javadocs]. | ||
+ | |||
+ | New users are encouraged to review the [[#QueryLanguageTutorial|Query Language Tutorial]] below. | ||
+ | |||
+ | ==<div id="QueryLanguageTutorial">Query Language Tutorial</div>== | ||
+ | The following is a brief tutorial on the Atlas query language. To follow along, you will need a Scala project for the shell and the sample project code to index. You can import them into your workspace from the following zip files: | ||
+ | |||
+ | *[http://cdn.ensoftcorp.com/atlas_docs/example-projects//Example-Shell-Plug-in.zip Download the Example Shell Project] | ||
+ | *[http://cdn.ensoftcorp.com/atlas_docs/example-projects//Example-Tutorial-Project.zip Download the Example Tutorial Project] | ||
+ | |||
+ | To import each project from the zip file using the steps below. | ||
+ | |||
+ | *Menu: <code>File -> Import...</code> | ||
+ | *Select "Existing Projects into Workspace" under "General" | ||
+ | *Use "Select archive file" and pick the zip file above. | ||
+ | *Select Finish. | ||
+ | |||
+ | This tutorial is divided into sections. | ||
+ | |||
+ | *[[#ScalaMini-tutorial|Scala Mini-tutorial]] | ||
+ | *[[#BasicAtlasQueries|Basic Atlas Queries]] | ||
+ | *[[#IntermediateAtlasQueries|Intermediate Atlas Queries]] | ||
+ | |||
+ | ===<div id="ScalaMini-tutorial">Scala Mini-tutorial</div>=== | ||
+ | This section assumes you have already performed the setup steps mentioned in start of [[#QueryLanguageTutorial|this guide]]. | ||
+ | |||
+ | The Atlas shell is a Scala shell with access to the Atlas index and API. Once you learn a few basics about Scala syntax, you will find it easy to write queries for Atlas. The Atlas API is written in Java, but since Scala seamlessly interoperates with Java, you have access to the full API, including the Java API itself. | ||
+ | |||
+ | To write a query in the shell, the expression is written in Scala. The first thing we will do is define a new variable. Copy the following into your shell, and push enter. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<code>var i = 42 </code> | <code>var i = 42 </code> | ||
− | + | ||
+ | That's it - you've just written something in Scala! The result of interpreting the expression will appear below, | ||
+ | |||
<code>i: Int = 42</code> | <code>i: Int = 42</code> | ||
− | + | ||
− | + | Notice that you did not have to specify the type of <code>i</code> - it was inferred from the value assigned to it. The type of <code>i</code> appears after the <code>:</code>. Note that <code>Int</code> is the Scala equivalent of Java's primitive <code>int</code>. | |
− | + | ||
− | + | Scala is a strongly-typed language. If you wish to declare the type of a variable, you add it <em>after</em> the variable name. | |
− | + | ||
− | + | <code>var d:Double = 42; </code> | |
+ | |||
+ | Once defined, you may use the variable. | ||
+ | |||
+ | <code>i = i + 17 </code> | ||
+ | |||
+ | You may also find it helpful to define a method. You can do this using <code>def</code>. The following defines a method called <code>inc</code> which takes an <code>Int</code> and returns that plus 17. | ||
+ | |||
<code>def inc(i:Int) = { i + 17 } </code> | <code>def inc(i:Int) = { i + 17 } </code> | ||
− | + | ||
+ | The equivalent in Java is: | ||
+ | |||
<code>int inc(int i) { return i + 17; } </code> | <code>int inc(int i) { return i + 17; } </code> | ||
− | + | ||
− | + | In the Scala version, note the <code>=</code> between the method name and the body - this is what returns the value of the last expression in the body. | |
+ | |||
+ | Once you have the method defined, you can use it like any other method. | ||
+ | |||
<code>inc(3) </code> | <code>inc(3) </code> | ||
− | + | ||
− | + | You may have noticed the lack of semi-colons. In Scala, the semi-colon tends to be optional, but when defining a method on a single line, you will find it helpful. | |
− | + | ||
− | + | Parenthesis for method calls are not always required either; however, for clarity, the tutorials will use parenthesis. | |
− | + | ||
− | + | On to [[#BasicAtlasQueries|Basic Atlas Queries]]. | |
+ | |||
+ | ==<div id="BasicAtlasQueries">Basic Atlas Queries</div>== | ||
+ | This section assumes you have already performed the setup steps mentioned in start of [[#QueryLanguageTutorial|this guide]]. | ||
+ | |||
+ | Before writing any queries, the Atlas has to index the workspace. When you start the shell, it will generally trigger indexing (or loading of a previous index). If you need to refresh the index, you can do so by typing: | ||
+ | |||
<code>indexWorkspace </code> | <code>indexWorkspace </code> | ||
− | + | ||
+ | If you need to clear any variables you've defined, you can restart the shell with the toolbar button or by typing: | ||
+ | |||
<code>:restart</code> | <code>:restart</code> | ||
− | + | ||
− | + | Note: you should restart the shell after re-indexing the workspace. This removes any variables which might be pointing to the previous index. | |
− | + | ||
+ | ===Your First Query=== | ||
+ | Your first Atlas query: ask for everything. Go ahead, it won't take long, we promise. | ||
+ | |||
<code>index</code> | <code>index</code> | ||
− | + | ||
− | + | In response, you'll see something similar to: | |
− | + | ||
− | + | <code>res0: com.ensoftcorp.atlas.core.query.Q = <Atlas query expression></code> | |
− | + | ||
− | + | What happened? | |
− | < | + | |
+ | First, we did not specify what to assign the result to, so the shell helpfully made a new variable called <code>res0</code> for the result. The type of the result is <code>com.ensoftcorp.atlas.core.query.Q</code>, and the string representation is <code><Atlas query expression></code>. | ||
+ | |||
+ | ===Evaluating the Index=== | ||
+ | The expression <code>index</code> is a starting point for Atlas queries based on <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>. In short, <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code> is a way to build up expressions which specify what you want, but nothing actually happens until you evaluate and start enumerating the result. | ||
+ | |||
+ | Evaluating <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code> will yield a <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/db/graph/Graph.html Graph]</code>, which has a set of nodes and a set of edges. Try that next. | ||
+ | |||
<code>index.eval() </code> | <code>index.eval() </code> | ||
− | + | ||
− | + | The result is a <code>Graph</code>. | |
− | + | ||
− | <div | + | <code>res1: com.ensoftcorp.atlas.core.db.graph.Graph = ....</code> |
− | <div | + | |
− | <div | + | Let's find out how many nodes and edges are in the index, shall we? |
− | + | ||
− | + | <div><code>var g = index.eval();</code></div> | |
− | + | <div><code>g.nodes().size();</code></div> | |
− | + | <div><code>g.edges().size();</code></div> | |
− | + | ||
− | + | You should see counts for the number of nodes and edges, respectively. | |
− | <div | + | |
− | <div | + | The key point here is that we write queries using <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code>, and <code>eval()</code> will return a <code>Graph</code> with nodes and edges. |
− | + | ||
+ | ===Seek and Display=== | ||
+ | Let's try some more interesting queries. | ||
+ | |||
+ | The <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code> interface contains a variety of methods for building expressions. These methods will return the query, allowing you to chain expressions together. | ||
+ | |||
+ | Starting from the index, we can select all types named "Base", using the query <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#types(java.lang.String) Q.types(java.lang.String)]</code>. By "types", we include classes, interfaces, enums and annotations. | ||
+ | |||
+ | <div><code>// all types named Base</code></div> | ||
+ | <div><code>var t = index.types("Base") </code></div> | ||
+ | |||
+ | Let's display the result in a graph editor. The <code>show()</code> method takes a <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#types(java.lang.String) Q.types(java.lang.String)]</code> directly, so no need to evaluate it first. | ||
+ | |||
<code>show(t)</code> | <code>show(t)</code> | ||
− | + | ||
− | + | [[File:basic-type.png|394px]] | |
− | + | ||
+ | You will see the class <code>Base</code>, along with its parent package and project. This is because <code>show()</code> is automatically providing some visual context by including the nodes which point to the result via an <code>XCSG.Contains</code> edge. | ||
+ | |||
+ | If you want to see the exact result of your query, which is sometimes helpful for debugging, you can tell <code>show()</code> not to extend your result. | ||
+ | |||
<code>show(t, extend=false)</code> | <code>show(t, extend=false)</code> | ||
− | + | ||
− | + | [[File:basic-type-no-extend.png|129px]] | |
− | + | ||
− | + | The above syntax, <code>extend=false</code> is the Scala way of passing a parameter value by naming the parameter. By default, <code>extend</code> is true. There are other parameters which can be set in this manner, including <code>highlighter</code> and <code>title</code>. | |
− | + | ||
− | + | Note: <code>show()</code> is defined in <code>com.ensoftcorp.atlas.ui.shell.lib.Common</code>. You can look at the code for <code>show()</code> yourself by using using Eclipse's <code>Open Type</code> feature (usually Ctrl-Space-T). | |
− | + | ||
− | + | ===Attributes and Tags=== | |
− | + | The <code>types()</code> query is a convenience for searching for all nodes with a given name and kind (i.e. Java types). By way of introducing other queries, we'll break this down into steps and search for both aspects separately. | |
− | <div | + | |
− | <div | + | Recall that evaluating <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html Q]</code> results in a <code>Graph</code>. <code>Graphs</code> have nodes and edges, which are represented by <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/db/graph/GraphElement.html GraphElement]</code>. <code>GraphElement</code> has both attributes and tags. The values of attributes and tags can be specified using queries, and we'll need both to implement our own <code>types()</code> query. |
− | + | ||
− | <div | + | The name of the node is encoded as an ''attribute''. An attribute key is a string, but best practice is to use the constant defined in the <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html XCSG]</code> interface. In this case, we need the key <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#name XCSG.name]</code>. |
− | <div | + | |
− | + | The kind of a node is encoded as a ''tag''. A tag is just a string, but again, best practice is to use the constant. The tag for Java types is <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#Type XCSG.Type]</code>. | |
− | <div | + | |
− | <div | + | Now we can find all the types with a given name step by step. First, select all the nodes with a given name using the query for node attributes, <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#selectNode(java.lang.String,%20java.lang.Object...) Q.selectNode()]</code>. |
− | + | ||
− | + | <div><code>// select by attribute</code></div> | |
− | + | <div><code>var named = index.selectNode(XCSG.name, "Base") </code></div> | |
− | + | ||
− | + | Next, select all nodes tagged <code>XCSG.Type</code> using the query <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#nodesTaggedWithAny(java.lang.String...) Q.nodesTaggedWithAny()]</code>. | |
− | <code>var result = index.nodesTaggedWithAny( | + | |
− | < | + | <div><code>// select by tag</code></div> |
− | + | <div><code>var tagged = index.nodesTaggedWithAny(XCSG.Type) </code></div> | |
− | + | ||
− | + | Finally, take the intersection. | |
− | + | ||
− | + | <div><code>var result = named.intersection(tagged);</code></div> | |
− | + | <div><code>show(result)</code></div> | |
− | <div | + | |
− | <div | + | [[File:basic-type.png|394px]] |
− | <div | + | |
− | <div | + | We now have the same result as if we had used <code>Q.types()</code> directly. |
− | <div | + | |
− | <div | + | Note: the interfaces <code>XCSG</code> is automatically imported, so one can simply write <code>Type</code> in a query. We will continue to fully qualify the constants for the remainder of this section for clarity. |
− | <div | + | |
− | <div | + | ===Chaining=== |
− | <div | + | Queries can build on one another directly. The following query has the same effect as the query in the previous section, accomplished in a slightly different way. From the index, the graph is reduced first by tags. From within that graph, nodes named "Base" are selected. |
− | + | ||
− | + | <div><code>var result = index.nodesTaggedWithAny(XCSG.Type)</code></div> | |
− | + | <div><code> .selectNode(XCSG.name, "Base") </code></div> | |
− | <div | + | |
− | <div | + | ===Walking the Graph=== |
− | <div | + | Now that we have a way to query for the type called "Base", what can we do with it? A simple thing to do is to get the type hierarchy. |
− | + | ||
− | + | In consulting the <code>XCSG</code> interface, we find that <code>XCSG.Type</code> nodes have edges tagged <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#Supertype XCSG.Supertype]</code> which point to their parent type. How can we query for the subtypes of "Base"? | |
− | + | ||
− | <div | + | We can walk the <code>XCSG.Supertype</code> edges down to the subtypes. Since a type points to its supertype, we walk the edges in the reverse direction to get the subtypes. To walk the edges, we will use the <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#reverse(com.ensoftcorp.atlas.core.query.Q) Q.reverse()]</code> query. |
− | <div | + | |
− | <div | + | But first, we have to set up the <code>Graph</code> in which the traversal will take place. The easiest way to walk over a certain kind of edge is to set up a filter on the edges. We accomplish this using the <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#edgesTaggedWithAny(java.lang.String...) Q.edgesTaggedWithAny()]</code> query. |
− | + | ||
− | + | The edges we are interested in are tagged <code>XCSG.Supertype</code>. | |
− | + | ||
− | + | Now, put it all together. | |
− | < | + | |
− | + | <div><code>// get a reference to Base</code></div> | |
− | + | <div><code>var base = index.types("Base")</code></div> | |
− | + | <div> </div> | |
− | + | <div><code>// filter edges</code></div> | |
− | <div | + | <div><code>var edgesSupertype = index.edgesTaggedWithAny(XCSG.Supertype)</code></div> |
− | <div | + | <div> </div> |
− | <div | + | <div><code>// walk to the subtypes from Base</code></div> |
− | + | <div><code>var result = edgesSupertype.reverse(base)</code></div> | |
− | + | <div><code>show(result)</code></div> | |
− | + | ||
− | <div | + | [[File:basic-supertypes.png|394px]] |
− | <div | + | |
− | <div | + | That's it - you now have a type hierarchy starting from "Base", and its subtypes. |
− | <div | + | |
− | <div | + | Note: in the future, you can use the convenience method <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/script/Common.html#edges(java.lang.String...) Common.edges()]</code> to filter edges. The equivalent query is: |
− | <div | + | |
− | <div | + | <div><code>var base = index.types("Base")</code></div> |
− | <div | + | <div><code>var result = edges(XCSG.Supertype).reverse(base)</code></div> |
− | <div | + | <div><code>show(result) </code></div> |
− | <div | + | |
− | + | ===Finding Methods Under a Type=== | |
− | + | Now that we know how to walk edges, let's query for everything under a type. Parent/child relationships between nodes are represented using edges tagged <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#Contains XCSG.Contains]</code>. | |
− | + | ||
− | <div | + | To expand the graph to include all the declarations immediately under a type, we can walk a single step along the <code>XCSG.Contains</code> edges, using the <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#forwardStep(java.lang.String...) Q.forwardStep()]</code> query. |
− | <div | + | |
− | <div | + | <div><code>var base = index.types("Base")</code></div> |
− | + | <div><code>var result = edges(XCSG.Contains).forwardStep(base)</code></div> | |
− | + | <div><code>show(result)</code></div> | |
− | + | ||
− | + | [[File:basic-methods-under-type.png|476px]] | |
− | + | ||
− | + | You should see several methods declared by "Base", including the method "helloWorld()". If you double-click on the method, Atlas will open and highlight the corresponding source code. | |
− | + | ||
− | + | ===Next...=== | |
− | + | On to [[#IntermediateAtlasQueries|Intermediate Atlas Queries]]. | |
− | + | ||
− | + | ==<div id="IntermediateAtlasQueries">Intermediate Atlas Queries</div>== | |
− | + | This section assumes you have already performed the setup steps mentioned in start of [[#QueryLanguageTutorial|this guide]]. | |
− | + | ||
− | + | ===A Forward Call Graph=== | |
− | + | Getting a forward call graph is a traversal, much like walking edges to get the type hierarchy. | |
− | + | ||
− | + | For this example, we will start the call graph from the method <code>com.ensoftcorp.atlas.java.example.project.Flow.foo()</code>. That's a lot to type in. Since the method name is reasonably unique, we can use the convenience method <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/script/Common.html#methodSelect(java.lang.String,%20java.lang.String) Common.methodSelect()]</code> , which allows us to partially qualify the method name. | |
− | + | ||
− | + | <div><code>var methodFoo = methodSelect("Flow", "foo");</code></div> | |
− | + | <div><code>var cg = edges(XCSG.Call).forward(methodFoo);</code></div> | |
− | <div | + | <div><code>show(cg);</code></div> |
− | <div | + | |
− | <div | + | [[File:int-cg.png|398px]] |
− | + | ||
− | + | ===Adding Some Color=== | |
− | + | A traversal might result in a fairly large graph, so it may help to add some color to call attention to where the traversal started. You can do that with a <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/highlight/Highlighter.html Highlighter]</code>. A <code>Highlighter</code> associates a color with a query expression, which is then passed to <code>show()</code> via the <code>highlighter</code> parameter. | |
− | + | ||
− | + | <div><code>// call graph query</code></div> | |
− | + | <div><code>var methodFoo = methodSelect("Flow", "foo");</code></div> | |
− | + | <div><code>var cg = edges(XCSG.Call).forward(methodFoo);</code></div> | |
− | + | <div> </div> | |
− | + | <div><code>// highlight the origin in red</code></div> | |
− | + | <div><code>var h = new Highlighter();</code></div> | |
− | + | <div><code>h.highlight(methodFoo, java.awt.Color.RED);</code></div> | |
− | + | <div> </div> | |
− | + | <div><code>// display</code></div> | |
− | <div | + | <div><code>show(cg, highlighter=h);</code></div> |
− | <div | + | |
− | <div | + | [[File:int-cg-color.png|397px]] |
− | <div | + | |
− | <div | + | ===A Reverse Call Graph=== |
− | <div | + | Reverse call graphs are specified in similar way as a forward call graph, walking backwards along <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#Call XCSG.Call]</code> edges. |
− | <div | + | |
− | <div | + | <div><code>var methodFoo = methodSelect("Flow", "print");</code></div> |
− | <div | + | <div><code>var cg = edges(XCSG.Call).reverse(methodFoo);</code></div> |
− | + | <div><code>show(cg);</code></div> | |
− | + | ||
− | + | [[File:int-rcg-1.png|397px]] | |
− | + | ||
− | + | You will notice one big difference between the forward call graph and the reverse call graph: the inclusion of control flow blocks, which appear in yellow. This is a feature: an <code>XCSG.Call</code> edge from a method represents the existence of one or more calls within the method. Likewise, there is an <code>XCSG.Call</code> edge at the control flow block granularity as well. | |
− | + | ||
− | + | ||
− | <div | + | ===Data Flow=== |
− | <div | + | Suppose that you want to find out where the values in a field flow to. Atlas provides data flow edges which you can query to find out. |
− | <div | + | |
− | + | For this example, we will start from the field <code>Flow.source</code>. We will use the convenience method <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/script/Common.html#fieldSelect(java.lang.String,%20java.lang.String) Common.fieldSelect()]</code> to obtain the field, which works similarly to <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/script/Common.html#methodSelect(java.lang.String,%20java.lang.String) Common.methodSelect()]</code>. | |
− | + | ||
− | + | Starting from the field, we can walk forward over all of the data flow edges, tagged <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#DataFlow_Edge DataFlow_Edge]</code>. | |
− | + | ||
− | + | <div><code>var src = fieldSelect("Flow", "source");</code></div> | |
− | + | <div><code>var dataFlowGraph = edges(XCSG.DataFlow_Edge).forward(src);</code></div> | |
− | + | <div><code>show(dataFlowGraph);</code></div> | |
− | + | ||
− | + | [[File:int-dataflow.png|394px]] | |
− | + | ||
− | + | In the resulting graph, the data flow nodes are colored green, and appear nested in control flow blocks, which are colored yellow. | |
− | + | ||
− | + | For the example code, the forward data flow from <code>Flow.source</code> will show the flow being returned from <code>getSource()</code>, passing through <code>foo()</code> and <code>bar()</code>, ultimately ending at the field <code>Flow.sink</code>. | |
− | + | ||
+ | Also note that some edges are labeled local and others interprocedural. The tag <code>XCSG.DataFlow_Edge</code> overlaps several kinds of data flow edges, including: | ||
+ | |||
+ | *<code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#LocalDataFlow XCSG.LocalDataFlow]</code> | ||
+ | *<code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/xcsg/XCSG.html#InterproceduralDataFlow XCSG.InterproceduralDataFlow]</code> | ||
+ | |||
+ | These tags are more specific kinds of data flow edges. Edges tagged <code>XCSG.LocalDataFlow</code> represent local data flow within a method. Edges tagged with <code>XCSG.InterproceduralDataFlow</code> represent data flow between methods, including flows to parameters, from the return statement of a method, or to and from fields. | ||
+ | |||
+ | ===Filling in Edges=== | ||
+ | Suppose that you have the nodes of interest, but need to fill in the edges in order to see the direct relationships between the nodes. You could use traversals, but it may be awkward to ensure that traversal does not add more nodes to the result. In these cases, it is more convenient to simply define the result in terms of the nodes and the space of edges. | ||
+ | |||
+ | By using [http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#induce(com.ensoftcorp.atlas.core.query.Q) induce()], one can specify additional edges to fill in. The edges are only added if the nodes they connect are already in the graph, so it is only necessary to specify the kinds of edges to add. | ||
+ | |||
+ | As an example, we construct a call graph by starting from the methods of interest, and add the call edges afterwards. | ||
+ | |||
+ | <div><code>// obtain methods</code></div> | ||
+ | <div><code>var parentType = index.types("Flow");</code></div> | ||
+ | <div><code>var members = edges(XCSG.Contains).forwardStep(parentType);</code></div> | ||
+ | <div><code>var methodsInFlow = members.nodesTaggedWithAny(XCSG.Method);</code></div> | ||
+ | <div> </div> | ||
+ | <div><code>// fill in call edges</code></div> | ||
+ | <div><code>var callEdges = index.edgesTaggedWithAll(XCSG.Call);</code></div> | ||
+ | <div><code>var cg = methodsInFlow.induce(callEdges);</code></div> | ||
+ | <div><code>show(cg);</code></div> | ||
+ | |||
+ | [[File:int-induce.png|414px]] | ||
+ | |||
+ | The resulting call graph includes only methods from the class <code>Flow</code>, and the call relationships between them. | ||
+ | |||
+ | ===Focusing on Edges=== | ||
+ | At some point, you may wish to see all the edges of a particular kind, without specifying the nodes that they are connected to. | ||
+ | |||
+ | You will recall that edges can be filtered using the query <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#edgesTaggedWithAny(java.lang.String...) Q.edgesTaggedWithAny()]</code>. If you display the result of such a query, you will notice that ''all'' of the nodes in the index are included. This is by design - it ensures that the starting point of a traversal query is always included in the answer, even if the starting point itself is not connected along the edges of interest. | ||
+ | |||
+ | To trim the graph to just the nodes which are connected to an edge in the graph, use the query <code>[http://www.ensoftcorp.com/atlas_docs/javadoc/2.x/com/ensoftcorp/atlas/core/query/Q.html#retainEdges() Q.retainEdges]</code>. For a small project, this is a useful way to get a better understanding of how particular kinds of edges connect. | ||
+ | |||
+ | For example, to see all the local and interprocedural data flow edges (and the nodes they connect) in the sample project, try the following. | ||
+ | |||
+ | <div><code>var dataFlowEdges = index.edgesTaggedWithAny(</code> <code> XCSG.InterproceduralDataFlow, XCSG.LocalDataFlow);</code></div> | ||
+ | <div><code>var connected = dataFlowEdges.retainEdges();</code></div> | ||
+ | <div><code>show(connected);</code></div> | ||
+ | |||
+ | [[File:int-edges.png|500px]] | ||
+ | |||
+ | =<div id="section6">Troubleshooting</div>= | ||
+ | ==General== | ||
+ | Atlas logs errors and status messages to the Eclipse Error Log. If there are problems indexing the code, or if there are more serious runtime errors, you should check the log. The Eclipse Error Log can be opened in Eclipse using the Window menu: <code>Window -> Show View -> Other...</code> Then, under General, select the "Error Log" view. | ||
+ | |||
+ | ==Atlas Shell== | ||
+ | ===Query results are not as expected=== | ||
+ | There a lot of reasons why a query might not return the results you expected. Here are a few basic steps to try next: | ||
+ | |||
+ | *If the code which you indexed with Atlas changed, you may need to index the code again. You can do this via the Atlas menu: <code>Atlas -> Index Workspace</code> | ||
+ | *If you re-index, the shell may be referring to data from a prior index. Restart the shell by entering <code>:restart</code> in the shell prompt. | ||
+ | *If the code in your shell plug-in changed, you may need to restart the shell to load the latest version of the code <span>by entering <code>:restart</code> in the shell prompt. | ||
+ | |||
+ | If you suspect a logical error in the query, you should try breaking the query into smaller parts, and display each step separately. |
Latest revision as of 12:45, 5 May 2020
The easiest way to get started with Atlas is Smart Views, but sometimes you need a different kind of graph, statistics, or even your own custom tooling. That's where the Atlas Shell comes in. The shell allows you to execute commands interactively using our Scala-based scripting language or to write scripts using Java, Scala, or any other JVM compatible language.
Contents
Configuring a Shell Project
The Atlas Shell requires a Scala project in the workspace for context (mostly for establishing the classpath). This project can also be used to build up longer scripts and utilities, which may be written in either Java, Scala, or any mix of JVM compatible languages.
To configure the shell project please follow the instructions in the video below. You can download the zip file referenced in the video here: Example-Shell-Plug-in.zip.
Importing a Scala Project Template
For convenience, a sample Scala project is supplied in this Example Shell zip file. You can import the project from the zip file using the steps below.
- Menu:
File -> Import...
- Select "Existing Projects into Workspace" under "General"
- Use "Select archive file" and pick the zip file above.
- Select Finish.
If you would prefer to create the Scala project manually, refer to the section Manually Creating a Scala Project below.
Manually Creating a Scala Project for use with the Shell
Create a Plug-in project, then convert it into a Scala project.
Then, to set the classpath for use with Atlas, open the manifest and add as dependencies:
- the plugins included with the Scala IDE (org.scala*, scalariform)
- the Atlas plugins
com.ensoftcorp.atlas
com.ensoftcorp.atlas.db
com.ensoftcorp.atlas.db.common
com.ensoftcorp.atlas.db.implementation
com.ensoftcorp.atlas.core
com.ensoftcorp.atlas.ui
com.ensoftcorp.atlas.ui.shell
Finally, disable indexing of the Scala project from the menu item Atlas -> Manage Project Settings
.
Executing Commands Interactively
Once you have configured a shell project right click on the project and select the menu item: Atlas -> Open Shell
. The shell will open and automatically index your project if the Atlas Smart View has not done so already.
Let's try a simple command. Type in the command below and then press enter. Now you know how many private fields are in your indexed projects.
index.nodesTaggedWithAll(XCSG.Field,XCSG.privateVisibility).eval.nodes.size
To create custom graphs, scripts, and much more follow the Query Language Tutorial below.
What kind of information can I query?
Atlas indexes source code in the workspace, and produces an index which is essentially a graph. The entire graph is usually referred to as the "index". The Atlas query language is an internal DSL embedded within Java. The primary interface used to build queries is Q
. Queries written using Q
are evaluated, yielding a Graph
, which is a subset of the index. One may write queries entirely in terms of Graph
, but most routine queries are easier to write using Q
.
The index currently contains:
- The major declarations (projects, types, packages, fields, methods), and associated relationships, such as the type hierarchy.
- A "summary" graph, which includes method and control-flow-granularity relationships, such as calls and reads/writes of fields, among other things.
- Control flow.
- Data flow.
Whether using Graph
or Q
, most queries will involve use of the constants defined in XCSG, which essentially forms the schema for the index. Studying the javadocs for this interface is highly recommended. In addition to the above, the Common class provides a handful of convenience methods for writing queries. In particular, Common.index()
is the starting point for a query based on Q
.
Additional documentation about the query language, graph data structures and more can be found in the Atlas javadocs.
New users are encouraged to review the Query Language Tutorial below.
Query Language Tutorial
The following is a brief tutorial on the Atlas query language. To follow along, you will need a Scala project for the shell and the sample project code to index. You can import them into your workspace from the following zip files:
To import each project from the zip file using the steps below.
- Menu:
File -> Import...
- Select "Existing Projects into Workspace" under "General"
- Use "Select archive file" and pick the zip file above.
- Select Finish.
This tutorial is divided into sections.
Scala Mini-tutorial
This section assumes you have already performed the setup steps mentioned in start of this guide.
The Atlas shell is a Scala shell with access to the Atlas index and API. Once you learn a few basics about Scala syntax, you will find it easy to write queries for Atlas. The Atlas API is written in Java, but since Scala seamlessly interoperates with Java, you have access to the full API, including the Java API itself.
To write a query in the shell, the expression is written in Scala. The first thing we will do is define a new variable. Copy the following into your shell, and push enter.
var i = 42
That's it - you've just written something in Scala! The result of interpreting the expression will appear below,
i: Int = 42
Notice that you did not have to specify the type of i
- it was inferred from the value assigned to it. The type of i
appears after the :
. Note that Int
is the Scala equivalent of Java's primitive int
.
Scala is a strongly-typed language. If you wish to declare the type of a variable, you add it after the variable name.
var d:Double = 42;
Once defined, you may use the variable.
i = i + 17
You may also find it helpful to define a method. You can do this using def
. The following defines a method called inc
which takes an Int
and returns that plus 17.
def inc(i:Int) = { i + 17 }
The equivalent in Java is:
int inc(int i) { return i + 17; }
In the Scala version, note the =
between the method name and the body - this is what returns the value of the last expression in the body.
Once you have the method defined, you can use it like any other method.
inc(3)
You may have noticed the lack of semi-colons. In Scala, the semi-colon tends to be optional, but when defining a method on a single line, you will find it helpful.
Parenthesis for method calls are not always required either; however, for clarity, the tutorials will use parenthesis.
On to Basic Atlas Queries.
Basic Atlas Queries
This section assumes you have already performed the setup steps mentioned in start of this guide.
Before writing any queries, the Atlas has to index the workspace. When you start the shell, it will generally trigger indexing (or loading of a previous index). If you need to refresh the index, you can do so by typing:
indexWorkspace
If you need to clear any variables you've defined, you can restart the shell with the toolbar button or by typing:
:restart
Note: you should restart the shell after re-indexing the workspace. This removes any variables which might be pointing to the previous index.
Your First Query
Your first Atlas query: ask for everything. Go ahead, it won't take long, we promise.
index
In response, you'll see something similar to:
res0: com.ensoftcorp.atlas.core.query.Q = <Atlas query expression>
What happened?
First, we did not specify what to assign the result to, so the shell helpfully made a new variable called res0
for the result. The type of the result is com.ensoftcorp.atlas.core.query.Q
, and the string representation is <Atlas query expression>
.
Evaluating the Index
The expression index
is a starting point for Atlas queries based on Q
. In short, Q
is a way to build up expressions which specify what you want, but nothing actually happens until you evaluate and start enumerating the result.
Evaluating Q
will yield a Graph
, which has a set of nodes and a set of edges. Try that next.
index.eval()
The result is a Graph
.
res1: com.ensoftcorp.atlas.core.db.graph.Graph = ....
Let's find out how many nodes and edges are in the index, shall we?
var g = index.eval();
g.nodes().size();
g.edges().size();
You should see counts for the number of nodes and edges, respectively.
The key point here is that we write queries using Q
, and eval()
will return a Graph
with nodes and edges.
Seek and Display
Let's try some more interesting queries.
The Q
interface contains a variety of methods for building expressions. These methods will return the query, allowing you to chain expressions together.
Starting from the index, we can select all types named "Base", using the query Q.types(java.lang.String)
. By "types", we include classes, interfaces, enums and annotations.
// all types named Base
var t = index.types("Base")
Let's display the result in a graph editor. The show()
method takes a Q.types(java.lang.String)
directly, so no need to evaluate it first.
show(t)
You will see the class Base
, along with its parent package and project. This is because show()
is automatically providing some visual context by including the nodes which point to the result via an XCSG.Contains
edge.
If you want to see the exact result of your query, which is sometimes helpful for debugging, you can tell show()
not to extend your result.
show(t, extend=false)
The above syntax, extend=false
is the Scala way of passing a parameter value by naming the parameter. By default, extend
is true. There are other parameters which can be set in this manner, including highlighter
and title
.
Note: show()
is defined in com.ensoftcorp.atlas.ui.shell.lib.Common
. You can look at the code for show()
yourself by using using Eclipse's Open Type
feature (usually Ctrl-Space-T).
Attributes and Tags
The types()
query is a convenience for searching for all nodes with a given name and kind (i.e. Java types). By way of introducing other queries, we'll break this down into steps and search for both aspects separately.
Recall that evaluating Q
results in a Graph
. Graphs
have nodes and edges, which are represented by GraphElement
. GraphElement
has both attributes and tags. The values of attributes and tags can be specified using queries, and we'll need both to implement our own types()
query.
The name of the node is encoded as an attribute. An attribute key is a string, but best practice is to use the constant defined in the XCSG
interface. In this case, we need the key XCSG.name
.
The kind of a node is encoded as a tag. A tag is just a string, but again, best practice is to use the constant. The tag for Java types is XCSG.Type
.
Now we can find all the types with a given name step by step. First, select all the nodes with a given name using the query for node attributes, Q.selectNode()
.
// select by attribute
var named = index.selectNode(XCSG.name, "Base")
Next, select all nodes tagged XCSG.Type
using the query Q.nodesTaggedWithAny()
.
// select by tag
var tagged = index.nodesTaggedWithAny(XCSG.Type)
Finally, take the intersection.
var result = named.intersection(tagged);
show(result)
We now have the same result as if we had used Q.types()
directly.
Note: the interfaces XCSG
is automatically imported, so one can simply write Type
in a query. We will continue to fully qualify the constants for the remainder of this section for clarity.
Chaining
Queries can build on one another directly. The following query has the same effect as the query in the previous section, accomplished in a slightly different way. From the index, the graph is reduced first by tags. From within that graph, nodes named "Base" are selected.
var result = index.nodesTaggedWithAny(XCSG.Type)
.selectNode(XCSG.name, "Base")
Walking the Graph
Now that we have a way to query for the type called "Base", what can we do with it? A simple thing to do is to get the type hierarchy.
In consulting the XCSG
interface, we find that XCSG.Type
nodes have edges tagged XCSG.Supertype
which point to their parent type. How can we query for the subtypes of "Base"?
We can walk the XCSG.Supertype
edges down to the subtypes. Since a type points to its supertype, we walk the edges in the reverse direction to get the subtypes. To walk the edges, we will use the Q.reverse()
query.
But first, we have to set up the Graph
in which the traversal will take place. The easiest way to walk over a certain kind of edge is to set up a filter on the edges. We accomplish this using the Q.edgesTaggedWithAny()
query.
The edges we are interested in are tagged XCSG.Supertype
.
Now, put it all together.
// get a reference to Base
var base = index.types("Base")
// filter edges
var edgesSupertype = index.edgesTaggedWithAny(XCSG.Supertype)
// walk to the subtypes from Base
var result = edgesSupertype.reverse(base)
show(result)
That's it - you now have a type hierarchy starting from "Base", and its subtypes.
Note: in the future, you can use the convenience method Common.edges()
to filter edges. The equivalent query is:
var base = index.types("Base")
var result = edges(XCSG.Supertype).reverse(base)
show(result)
Finding Methods Under a Type
Now that we know how to walk edges, let's query for everything under a type. Parent/child relationships between nodes are represented using edges tagged XCSG.Contains
.
To expand the graph to include all the declarations immediately under a type, we can walk a single step along the XCSG.Contains
edges, using the Q.forwardStep()
query.
var base = index.types("Base")
var result = edges(XCSG.Contains).forwardStep(base)
show(result)
You should see several methods declared by "Base", including the method "helloWorld()". If you double-click on the method, Atlas will open and highlight the corresponding source code.
Next...
On to Intermediate Atlas Queries.
Intermediate Atlas Queries
This section assumes you have already performed the setup steps mentioned in start of this guide.
A Forward Call Graph
Getting a forward call graph is a traversal, much like walking edges to get the type hierarchy.
For this example, we will start the call graph from the method com.ensoftcorp.atlas.java.example.project.Flow.foo()
. That's a lot to type in. Since the method name is reasonably unique, we can use the convenience method Common.methodSelect()
, which allows us to partially qualify the method name.
var methodFoo = methodSelect("Flow", "foo");
var cg = edges(XCSG.Call).forward(methodFoo);
show(cg);
Adding Some Color
A traversal might result in a fairly large graph, so it may help to add some color to call attention to where the traversal started. You can do that with a Highlighter
. A Highlighter
associates a color with a query expression, which is then passed to show()
via the highlighter
parameter.
// call graph query
var methodFoo = methodSelect("Flow", "foo");
var cg = edges(XCSG.Call).forward(methodFoo);
// highlight the origin in red
var h = new Highlighter();
h.highlight(methodFoo, java.awt.Color.RED);
// display
show(cg, highlighter=h);
A Reverse Call Graph
Reverse call graphs are specified in similar way as a forward call graph, walking backwards along XCSG.Call
edges.
var methodFoo = methodSelect("Flow", "print");
var cg = edges(XCSG.Call).reverse(methodFoo);
show(cg);
You will notice one big difference between the forward call graph and the reverse call graph: the inclusion of control flow blocks, which appear in yellow. This is a feature: an XCSG.Call
edge from a method represents the existence of one or more calls within the method. Likewise, there is an XCSG.Call
edge at the control flow block granularity as well.
Data Flow
Suppose that you want to find out where the values in a field flow to. Atlas provides data flow edges which you can query to find out.
For this example, we will start from the field Flow.source
. We will use the convenience method Common.fieldSelect()
to obtain the field, which works similarly to Common.methodSelect()
.
Starting from the field, we can walk forward over all of the data flow edges, tagged DataFlow_Edge
.
var src = fieldSelect("Flow", "source");
var dataFlowGraph = edges(XCSG.DataFlow_Edge).forward(src);
show(dataFlowGraph);
In the resulting graph, the data flow nodes are colored green, and appear nested in control flow blocks, which are colored yellow.
For the example code, the forward data flow from Flow.source
will show the flow being returned from getSource()
, passing through foo()
and bar()
, ultimately ending at the field Flow.sink
.
Also note that some edges are labeled local and others interprocedural. The tag XCSG.DataFlow_Edge
overlaps several kinds of data flow edges, including:
These tags are more specific kinds of data flow edges. Edges tagged XCSG.LocalDataFlow
represent local data flow within a method. Edges tagged with XCSG.InterproceduralDataFlow
represent data flow between methods, including flows to parameters, from the return statement of a method, or to and from fields.
Filling in Edges
Suppose that you have the nodes of interest, but need to fill in the edges in order to see the direct relationships between the nodes. You could use traversals, but it may be awkward to ensure that traversal does not add more nodes to the result. In these cases, it is more convenient to simply define the result in terms of the nodes and the space of edges.
By using induce(), one can specify additional edges to fill in. The edges are only added if the nodes they connect are already in the graph, so it is only necessary to specify the kinds of edges to add.
As an example, we construct a call graph by starting from the methods of interest, and add the call edges afterwards.
// obtain methods
var parentType = index.types("Flow");
var members = edges(XCSG.Contains).forwardStep(parentType);
var methodsInFlow = members.nodesTaggedWithAny(XCSG.Method);
// fill in call edges
var callEdges = index.edgesTaggedWithAll(XCSG.Call);
var cg = methodsInFlow.induce(callEdges);
show(cg);
The resulting call graph includes only methods from the class Flow
, and the call relationships between them.
Focusing on Edges
At some point, you may wish to see all the edges of a particular kind, without specifying the nodes that they are connected to.
You will recall that edges can be filtered using the query Q.edgesTaggedWithAny()
. If you display the result of such a query, you will notice that all of the nodes in the index are included. This is by design - it ensures that the starting point of a traversal query is always included in the answer, even if the starting point itself is not connected along the edges of interest.
To trim the graph to just the nodes which are connected to an edge in the graph, use the query Q.retainEdges
. For a small project, this is a useful way to get a better understanding of how particular kinds of edges connect.
For example, to see all the local and interprocedural data flow edges (and the nodes they connect) in the sample project, try the following.
var dataFlowEdges = index.edgesTaggedWithAny(
XCSG.InterproceduralDataFlow, XCSG.LocalDataFlow);
var connected = dataFlowEdges.retainEdges();
show(connected);
Troubleshooting
General
Atlas logs errors and status messages to the Eclipse Error Log. If there are problems indexing the code, or if there are more serious runtime errors, you should check the log. The Eclipse Error Log can be opened in Eclipse using the Window menu: Window -> Show View -> Other...
Then, under General, select the "Error Log" view.
Atlas Shell
Query results are not as expected
There a lot of reasons why a query might not return the results you expected. Here are a few basic steps to try next:
- If the code which you indexed with Atlas changed, you may need to index the code again. You can do this via the Atlas menu:
Atlas -> Index Workspace
- If you re-index, the shell may be referring to data from a prior index. Restart the shell by entering
:restart
in the shell prompt. - If the code in your shell plug-in changed, you may need to restart the shell to load the latest version of the code by entering
:restart
in the shell prompt.
If you suspect a logical error in the query, you should try breaking the query into smaller parts, and display each step separately.