Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java: Improve concatenation in loops #16859

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
25 changes: 16 additions & 9 deletions java/ql/src/Performance/ConcatenationInLoops.ql
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,26 @@
)
}

predicate declaredInLoop(LocalVariableDecl v, LoopStmt loop) {
exists(LocalVariableDeclExpr e |
e.getVariable() = v and
e.getEnclosingStmt().getEnclosingStmt*() = loop.getBody()
)
/**
* Holds if `e` is executed often in loop `loop`.
*/
predicate executedOften(Assignment a) {

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for a, but the QLDoc mentions loop
a.getDest().getType() instanceof TypeString and
exists(ControlFlowNode n | a.getControlFlowNode() = n | getADeepSuccessor(n) = n)
}

/** Gets a sucessor of `n`, also following function calls. */

Check warning

Code scanning / CodeQL

Misspelling Warning

This comment contains the common misspelling 'sucessor', which should instead be 'successor'.
ControlFlowNode getADeepSuccessor(ControlFlowNode n) {
result = n.getASuccessor+()
or
exists(EnhancedForStmt for | for = loop | for.getVariable().getVariable() = v)
exists(Call c, ControlFlowNode callee | c.(Expr).getControlFlowNode() = n.getASuccessor+() |
callee = c.getCallee().getBody().getControlFlowNode() and
result = getADeepSuccessor(callee)
)
}

from Assignment a, Variable v
where
useAndDef(a, v) and
exists(LoopStmt loop | a.getEnclosingStmt().getEnclosingStmt*() = loop |
not declaredInLoop(v, loop)
)
executedOften(a)
select a, "The string " + v.getName() + " is built-up in a loop: use string buffer."
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
| ConcatenationInLoops.java:9:4:9:10 | ...+=... | The string s is built-up in a loop: use string buffer. |
| ConcatenationInLoops.java:32:6:32:12 | ...+=... | The string s is built-up in a loop: use string buffer. |
| ConcatenationInLoops.java:39:4:39:12 | ...+=... | The string cs is built-up in a loop: use string buffer. |
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
public class ConcatenationInLoops {
private String cs = "";

public static void main(String[] args) {
// Random r = 42;
long start = System.currentTimeMillis();
String s = "";
for (int i = 0; i < 65536; i++)
s += 42; // $ loopConcat=s
System.out.println(System.currentTimeMillis() - start); // This prints roughly 4500.

// r = 42;
start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 65536; i++)
sb.append(42);//r.nextInt(2));
s = sb.toString();
System.out.println(System.currentTimeMillis() - start); // This prints 5.


String s2 = "";
for (int i = 0; i < 65536; i++)
if (i > 10) {
s += 42; // Will only be executed once.
break;
}

String s3 = "";
for (int i = 0; i < 65536; i++)
for (int j = 0; i < 65536; i++)
if (j > 10) {
s += 42; // $ loopConcat=s
break;
}
}

public void test(int bound) {
if (bound > 0) {
cs += "a"; // $ loopConcat=cs
test(bound - 1);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Performance/ConcatenationInLoops.ql
2 changes: 2 additions & 0 deletions java/ql/test/query-tests/ConcatenationInLoops/test.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
testFailures
failures
82 changes: 82 additions & 0 deletions java/ql/test/query-tests/ConcatenationInLoops/test.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import java
import semmle.code.java.dataflow.DataFlow
import TestUtilities.InlineExpectationsTest

module TheQuery {
import semmle.code.java.Type
import semmle.code.java.Expr

Check warning

Code scanning / CodeQL

Redundant import Warning

Redundant import, the module is already imported inside
semmle.code.java.Statement
.
import semmle.code.java.Statement
import semmle.code.java.JDK

Check warning

Code scanning / CodeQL

Redundant import Warning

Redundant import, the module is already imported inside
semmle.code.java.Type
.

/**
* An assignment of the form
*
* ```
* v = ... + ... v ...
* ```
* or
*
* ```
* v += ...
* ```
* where `v` is a simple variable (and not, for example,
* an array element).
*/
Comment on lines +11 to +24

Check warning

Code scanning / CodeQL

Predicate QLDoc style. Warning

The QLDoc for a predicate without a result should start with 'Holds'.
predicate useAndDef(Assignment a, Variable v) {
a.getDest() = v.getAnAccess() and
v.getType() instanceof TypeString and
(
a instanceof AssignAddExpr
or
exists(VarAccess use | use.getVariable() = v | use.getParent*() = a.getSource()) and
a.getSource() instanceof AddExpr
)
}

/**
* Holds if `e` is executed often in loop `loop`.
*/
predicate executedOften(Assignment a) {

Check warning

Code scanning / CodeQL

Missing QLDoc for parameter Warning

The QLDoc has no documentation for a, but the QLDoc mentions loop
a.getDest().getType() instanceof TypeString and
exists(ControlFlowNode n | a.getControlFlowNode() = n | getADeepSuccessor(n) = n)
}

/** Gets a sucessor of `n`, also following function calls. */

Check warning

Code scanning / CodeQL

Misspelling Warning

This comment contains the common misspelling 'sucessor', which should instead be 'successor'.
ControlFlowNode getADeepSuccessor(ControlFlowNode n) {
result = n.getASuccessor+()
or
exists(Call c, ControlFlowNode callee | c.(Expr).getControlFlowNode() = n.getASuccessor+() |
callee = c.getCallee().getBody().getControlFlowNode() and
result = getADeepSuccessor(callee)
)
}

predicate queryResult(Assignment a) {
exists(Variable v |

Check warning

Code scanning / CodeQL

Omittable 'exists' variable Warning

This exists variable can be omitted by using a don't-care expression
in this argument
.
useAndDef(a, v) and
executedOften(a)
)
}
}

// module Config implements DataFlow::ConfigSig {
// predicate isSource(DataFlow::Node n) { n.asExpr().(MethodCall).getMethod().hasName("source") }
// predicate isSink(DataFlow::Node n) {
// exists(MethodCall ma | ma.getMethod().hasName("sink") | n.asExpr() = ma.getAnArgument())
// }
// }
// module Flow = DataFlow::Global<Config>;
module HasFlowTest implements TestSig {
string getARelevantTag() { result = "loopConcat" }

predicate hasActualResult(Location location, string element, string tag, string value) {
tag = "loopConcat" and
exists(Assignment a | TheQuery::queryResult(a) |
location = a.getLocation() and
element = a.toString() and
value = a.getDest().toString()
)
}
}

import MakeTest<HasFlowTest>
Loading