Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -681,7 +681,7 @@ public DynamicMethod putMethod(ThreadContext context, String id, DynamicMethod m
method.setImplementationClass(methodLocation);
}

if (context.runtime.isCoverageEnabled()) context.runtime.getCoverageData().registerMethod(id, method);
if (context.runtime.isCoverageEnabled()) context.runtime.getCoverageData().registerMethod(method);

DynamicMethod oldMethod = methodLocation.getMethodsForWrite().put(id, method);

Expand Down
7 changes: 7 additions & 0 deletions core/src/main/java/org/jruby/ast/CallNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ public Node getReceiverNode() {
public boolean isLazy() {
return isLazy;
}

@Override
public void setAutoSourceSpan(long start, long end) {
// the call keeps the span of the production that created it; a block attached by an enclosing
// production is not part of the call's own source (as MRI reports it)
if (!hasSourceSpan()) super.setAutoSourceSpan(start, end);
}

public List<Node> childNodes() {
return Node.createList(receiverNode, argsNode, iterNode);
Expand Down
89 changes: 89 additions & 0 deletions core/src/main/java/org/jruby/ast/IfNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.List;

import org.jruby.ast.visitor.NodeVisitor;
import org.jruby.parser.ProductionState;

/**
* an 'if' statement.
Expand Down Expand Up @@ -94,4 +95,92 @@ public Node getThenBody() {
public List<Node> childNodes() {
return Node.createList(condition, thenBody, elseBody);
}

// ---- branch coverage (recorded by the parser, read by the IR builder) ----

private boolean branch; // a Ruby-level conditional MRI reports (not e.g. a pattern guard)
private boolean unless; // written as unless: then/else bodies are swapped
private boolean elsif; // an elsif clause of an enclosing if
private int predicateEndLine = -1; // zero-based position just past the condition (an empty then arm is reported there)
private int predicateEndColumn = -1;
private int elseStartLine = -1; // zero-based position of the 'else' keyword, when there is one
private int elseStartColumn = -1;
private Node sourceBody; // for a modifier: the statement as written (before begin/end unwrapping)
private int constantPredicate; // 0: not a literal; 1: a literal MRI folds to true; -1: one it folds to false

public void markBranch(boolean unless, boolean elsif, long predicateEnd, long elseStart) {
this.branch = true;
this.unless = unless;
this.elsif = elsif;
if (predicateEnd >= 0) {
predicateEndLine = ProductionState.line(predicateEnd);
predicateEndColumn = ProductionState.column(predicateEnd);
}
if (elseStart >= 0) {
elseStartLine = ProductionState.line(elseStart);
elseStartColumn = ProductionState.column(elseStart);
}
}

public boolean isBranch() {
return branch;
}

public boolean isUnless() {
return unless;
}

public boolean isElsif() {
return elsif;
}

public boolean hasPredicateEnd() {
return predicateEndColumn >= 0;
}

public int getPredicateEndLine() {
return predicateEndLine;
}

public int getPredicateEndColumn() {
return predicateEndColumn;
}

public boolean hasElseStart() {
return elseStartColumn >= 0;
}

public int getElseStartLine() {
return elseStartLine;
}

public int getElseStartColumn() {
return elseStartColumn;
}

public void setSourceBody(Node sourceBody) {
this.sourceBody = sourceBody;
}

/**
* MRI folds a conditional on a literal predicate away, reporting no branch for it and compiling nothing of
* the arm that cannot run.
*
* @param constantPredicate 0 when the predicate is not a literal, 1 when it is a truthy one, -1 a falsy one
*/
public void setConstantPredicate(int constantPredicate) {
this.constantPredicate = constantPredicate;
}

public boolean hasConstantPredicate() {
return constantPredicate != 0;
}

public boolean isConstantlyTrue() {
return constantPredicate > 0;
}

public Node getSourceBody() {
return sourceBody;
}
}
24 changes: 24 additions & 0 deletions core/src/main/java/org/jruby/ast/InNode.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.jruby.ast;

import org.jruby.ast.visitor.NodeVisitor;
import org.jruby.parser.ProductionState;

import java.util.List;

Expand Down Expand Up @@ -48,4 +49,27 @@ public List<Node> childNodes() {
public NodeType getNodeType() {
return NodeType.INNODE;
}

// ---- branch coverage: where the 'else' keyword following this clause starts (when nextCase is an else body) ----

private int elseStartLine = -1;
private int elseStartColumn = -1;

public void setElseStart(long elseStart) {
if (elseStart < 0) return;
elseStartLine = ProductionState.line(elseStart);
elseStartColumn = ProductionState.column(elseStart);
}

public boolean hasElseStart() {
return elseStartColumn >= 0;
}

public int getElseStartLine() {
return elseStartLine;
}

public int getElseStartColumn() {
return elseStartColumn;
}
}
33 changes: 2 additions & 31 deletions core/src/main/java/org/jruby/ast/IterNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,6 @@ public class IterNode extends Node implements DefNode {

// What static scoping relationship exists when it comes into being.
private final StaticScope scope;
private int endLine;
private int startColumn = -1;
private int endColumn = -1;

/**
* Used by ForNode only.
Expand All @@ -60,7 +57,7 @@ public IterNode(int line, Node args, StaticScope scope, Node body, int endLine)
this.varNode = args;
this.scope = scope;
this.bodyNode = body;
this.endLine = endLine;
setSourceSpan(-1, endLine, -1);
}

/**
Expand All @@ -72,7 +69,7 @@ public IterNode(int line, ArgsNode args, Node body, StaticScope scope, int endLi
this.varNode = args;
this.bodyNode = body == null ? NilImplicitNode.NIL : body;
this.scope = scope;
this.endLine = endLine;
setSourceSpan(-1, endLine, -1);
}

public NodeType getNodeType() {
Expand Down Expand Up @@ -116,30 +113,4 @@ public List<Node> childNodes() {
return Node.createList(varNode, bodyNode);
}

public int getEndLine() {
return endLine;
}

public int getStartColumn() {
return startColumn;
}

public int getEndColumn() {
return endColumn;
}

/**
* Record the source span of this block: from its opening brace or do keyword through its closing brace or
* end keyword. For a lambda: from the start of the parameter list, or just after "->" when there is none,
* through the end of the body.
*
* @param startColumn zero-based byte column of the first character
* @param endLine zero-based line of the last character
* @param endColumn zero-based byte column just after the last character
*/
public void setSourceSpan(int startColumn, int endLine, int endColumn) {
this.startColumn = startColumn;
this.endLine = endLine;
this.endColumn = endColumn;
}
}
22 changes: 1 addition & 21 deletions core/src/main/java/org/jruby/ast/MethodDefNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ public abstract class MethodDefNode extends Node implements INameNode, DefNode {
protected final ArgsNode argsNode;
protected final StaticScope scope;
protected final Node bodyNode;
protected final int endLine;
protected final int startColumn;
protected final int endColumn;
// We lazily compile methods in IR but Ruby expects next and break to raise syntax error so we will eagerly
// build methods which contain those two keywords.
protected boolean containsNextBreak = false;
Expand All @@ -60,9 +57,7 @@ public MethodDefNode(int line, int startColumn, RubySymbol name, ArgsNode argsNo
this.argsNode = argsNode;
this.scope = scope;
this.bodyNode = bodyNode;
this.endLine = endLine;
this.startColumn = startColumn;
this.endColumn = endColumn;
setSourceSpan(startColumn, endLine, endColumn);
}

/**
Expand Down Expand Up @@ -99,21 +94,6 @@ public RubySymbol getName() {
return name;
}

/**
* Which line is the 'end' encountered on. Useful for RETURN event generation.
* @return the zero-based line number
*/
public int getEndLine() {
return endLine;
}

public int getStartColumn() {
return startColumn;
}

public int getEndColumn() {
return endColumn;
}

public void setContainsNextBreak() {
containsNextBreak = true;
Expand Down
5 changes: 5 additions & 0 deletions core/src/main/java/org/jruby/ast/NilImplicitNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
*/
public class NilImplicitNode extends NilNode implements InvisibleNode {
public static final NilImplicitNode NIL = new NilImplicitNode();

@Override
public void setAutoSourceSpan(long start, long end) {
// shared singleton: it has no position of its own
}

public NilImplicitNode() {
super(-1);
Expand Down
105 changes: 105 additions & 0 deletions core/src/main/java/org/jruby/ast/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

import org.jruby.ast.types.INameNode;
import org.jruby.ast.visitor.NodeVisitor;
import org.jruby.parser.ProductionState;

/**
* Base class for all Nodes in the AST
Expand All @@ -49,6 +50,17 @@ public abstract class Node {
static final List<Node> EMPTY_LIST = new ArrayList<>();

private int line;

// Source span of this node: the (zero-based) line and byte column of its first character and the line and
// column just past its last character. The parser records it (see RubyParser's skeleton) and Coverage uses
// it to identify methods and branches the way MRI does. -1 when unknown; startLine is only recorded when it
// differs from line (e.g. fixpos moves a modifier's line to its condition).
private int startLine = -1;
private int startColumn = -1;
private int endLine = -1;
private int endColumn = -1;
private boolean spanLocked; // set explicitly: productions passing the node along no longer widen it
private int[] parenSpan; // span of the parentheses written around this expression, if any
Comment thread
headius marked this conversation as resolved.

// Does this node contain a node which is an assignment. We can use this knowledge when emitting IR
// instructions to do more or less depending on whether we have to cope with scenarios like:
Expand Down Expand Up @@ -87,6 +99,99 @@ public String getFile() {
public void setLine(int line) {
this.line = line;
}

public boolean hasSourceSpan() {
return startColumn >= 0 && endLine >= 0;
}

/**
* Record the span from the parser's packed (line, column) positions (see ProductionState).
*/
public void setSourceSpan(long start, long end) {
startLine = ProductionState.line(start);
startColumn = ProductionState.column(start);
endLine = ProductionState.line(end);
endColumn = ProductionState.column(end);
spanLocked = true;
}

/**
* Record the span of a node starting on its own line (getLine()).
*/
public void setSourceSpan(int startColumn, int endLine, int endColumn) {
this.startLine = -1;
this.startColumn = startColumn;
this.endLine = endLine;
this.endColumn = endColumn;
spanLocked = true;
}

/**
* Keep the span as it is now: enclosing productions handing the node along no longer widen it.
*/
public void lockSourceSpan() {
spanLocked = true;
}

public void setSourceSpanEnd(long end) {
endLine = ProductionState.line(end);
endColumn = ProductionState.column(end);
spanLocked = true;
}

/**
* Record the span of a production that produced this node. Each enclosing production that hands the node
* along as its own result widens the span to its own extent (an assignment grows its left-hand side into the
* whole assignment, a statement list grows as statements are appended), until an explicit span locks it.
*/
public void setAutoSourceSpan(long start, long end) {
if (spanLocked) return;

startLine = ProductionState.line(start);
startColumn = ProductionState.column(start);
endLine = ProductionState.line(end);
endColumn = ProductionState.column(end);
}

public void copySourceSpan(Node other) {
startLine = other.startLine;
startColumn = other.startColumn;
endLine = other.endLine;
endColumn = other.endColumn;
spanLocked = other.spanLocked;
}

/**
* Record that this expression was written inside parentheses; MRI has a node for the parentheses and
* reports a branch arm consisting of a parenthesized expression at the parentheses.
*/
public void setParenSpan(long start, long end) {
parenSpan = new int[] { ProductionState.line(start), ProductionState.column(start), ProductionState.line(end), ProductionState.column(end) };
}

/**
* [start_line, start_column, end_line, end_column] (zero-based) of the parentheses written around this
* expression, or null.
*/
public int[] getParenSpan() {
return parenSpan;
}

public int getStartLine() {
return startLine >= 0 ? startLine : line;
}

public int getStartColumn() {
return startColumn;
}

public int getEndLine() {
return endLine;
}

public int getEndColumn() {
return endColumn;
}

public abstract <T> T accept(NodeVisitor<T> visitor);
public abstract List<Node> childNodes();
Expand Down
Loading
Loading