Skip to content

Commit d19c02c

Browse files
committed
JS: Restore dirname functionality in a simpler way
1 parent 89c6e1e commit d19c02c

4 files changed

Lines changed: 99 additions & 24 deletions

File tree

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
private import javascript
2+
3+
/**
4+
* A path expression that can be constant-folded by concatenating other paths.
5+
*/
6+
abstract class PathConcatenation extends Expr {
7+
/** Gets the separator to insert between paths */
8+
string getSeparator() { result = "" }
9+
10+
/** Gets the `n`th operand to concatenate. */
11+
abstract Expr getOperand(int n);
12+
}
13+
14+
private class AddExprConcatenation extends PathConcatenation, AddExpr {
15+
override Expr getOperand(int n) {
16+
n = 0 and result = this.getLeftOperand()
17+
or
18+
n = 1 and result = this.getRightOperand()
19+
}
20+
}
21+
22+
private class JoinCallConcatenation extends PathConcatenation, CallExpr {
23+
JoinCallConcatenation() {
24+
this.getReceiver().(VarAccess).getName() = "path" and
25+
this.getCalleeName() = "join"
26+
}
27+
28+
override Expr getOperand(int n) { result = this.getArgument(n) }
29+
30+
override string getSeparator() { result = "/" }
31+
}

javascript/ql/lib/semmle/javascript/internal/paths/PathExprResolver.qll

Lines changed: 56 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ private import javascript
22
private import semmle.javascript.TSConfig
33
private import semmle.javascript.internal.paths.PackageJsonEx
44
private import semmle.javascript.internal.paths.JSPaths
5+
private import semmle.javascript.internal.paths.PathConcatenation
6+
private import semmle.javascript.dataflow.internal.DataFlowNode
57

68
File getFileFromFolderImport(Folder folder) {
79
result = folder.getJavaScriptFileOrTypings("index")
@@ -14,15 +16,68 @@ File getFileFromFolderImport(Folder folder) {
1416
)
1517
}
1618

19+
private Variable dirname() { result.getName() = "__dirname" }
20+
21+
private Variable filename() { result.getName() = "__filename" }
22+
1723
private signature predicate exprSig(Expr e);
1824

1925
module ResolveExpr<exprSig/1 shouldResolveExpr> {
26+
/** Holds if we need the constant-value of `node`. */
27+
private predicate needsConstantFolding(EarlyStageNode node) {
28+
exists(Expr e |
29+
shouldResolveExpr(e) and
30+
node = TValueNode(e)
31+
)
32+
or
33+
exists(EarlyStageNode needsFolding | needsConstantFolding(needsFolding) |
34+
DataFlow::Impl::earlyStageImmediateFlowStep(node, needsFolding)
35+
or
36+
exists(PathConcatenation joiner |
37+
needsFolding = TValueNode(joiner) and
38+
node = TValueNode(joiner.getOperand(_))
39+
)
40+
)
41+
}
42+
43+
/** Gets the constant-value of `node` */
44+
language[monotonicAggregates]
45+
private string getValue(EarlyStageNode node) {
46+
needsConstantFolding(node) and
47+
(
48+
exists(Expr e | node = TValueNode(e) |
49+
result = e.getStringValue()
50+
or
51+
e = dirname().getAnAccess() and
52+
result = "./" // Ensure the path gets interpreted relative to the current directory
53+
or
54+
e = filename().getAnAccess() and
55+
result = "./" + e.getFile().getBaseName()
56+
)
57+
or
58+
exists(EarlyStageNode pred |
59+
DataFlow::Impl::earlyStageImmediateFlowStep(pred, node) and
60+
result = getValue(pred)
61+
)
62+
or
63+
exists(PathConcatenation join |
64+
node = TValueNode(join) and
65+
result =
66+
strictconcat(int n, EarlyStageNode child, string sep |
67+
child = TValueNode(join.getOperand(n)) and sep = join.getSeparator()
68+
|
69+
getValue(child), sep order by n
70+
)
71+
)
72+
)
73+
}
74+
2075
final private class FinalExpr = Expr;
2176

2277
private class RelevantExpr extends FinalExpr {
2378
RelevantExpr() { shouldResolveExpr(this) }
2479

25-
string getValue() { result = this.getStringValue() }
80+
string getValue() { result = getValue(TValueNode(this)) }
2681
}
2782

2883
/**
@@ -66,17 +121,6 @@ module ResolveExpr<exprSig/1 shouldResolveExpr> {
66121
result = expr.getValue().regexpFind("^(@[^/\\\\]+[/\\\\])?[^@./\\\\][^/\\\\]*", _, _)
67122
}
68123

69-
private Variable dirname() { result.getName() = "__dirname" }
70-
71-
/** Holds if `add` is a relevant path expression of form `__dirname + expr`. */
72-
private predicate prefixedByDirname(Expr expr) {
73-
expr = dirname().getAnAccess()
74-
or
75-
prefixedByDirname(expr.(AddExpr).getLeftOperand())
76-
or
77-
prefixedByDirname(expr.(CallExpr).getArgument(0))
78-
}
79-
80124
/**
81125
* Holds if `expr` matches a path mapping, and should thus be resolved as `newPath` relative to `base`.
82126
*/
@@ -139,12 +183,6 @@ module ResolveExpr<exprSig/1 shouldResolveExpr> {
139183
// Relative paths are resolved from their enclosing folder
140184
relativePathExpr(expr, base, path)
141185
or
142-
// Paths prefixed by __dirname should be resolved from the root dir, because __dirname
143-
// currently has a getValue() that returns its absolute path.
144-
prefixedByDirname(expr) and
145-
not exists(base.getParentContainer()) and // get root dir
146-
path = expr.getValue()
147-
or
148186
resolveViaPathMapping(expr, base, path)
149187
or
150188
// Resolve from baseUrl of relevant tsconfig.json file

javascript/ql/test/library-tests/PathResolution/DirnameImports/main.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,20 @@
22
const path = require('path');
33

44
// Using __dirname directly
5-
const direct = require(__dirname + '/target.js'); // $ MISSING: importTarget=DirnameImports/target.js
5+
const direct = require(__dirname + '/target.js'); // $ importTarget=DirnameImports/target.js
66

77
// Using __dirname with path.join
8-
const withPathJoin = require(path.join(__dirname, 'target.js')); // $ MISSING: importTarget=DirnameImports/target.js
8+
const withPathJoin = require(path.join(__dirname, 'target.js')); // $ importTarget=DirnameImports/target.js
99

1010
// Using __dirname with nested path
11-
const nested = require(__dirname + '/nested/target.js'); // $ MISSING: importTarget=DirnameImports/nested/target.js
11+
const nested = require(__dirname + '/nested/target.js'); // $ importTarget=DirnameImports/nested/target.js
1212

1313
// Using __dirname with parent directory
14-
const parent = require(__dirname + '/../import-packages.ts'); // $ MISSING: importTarget=import-packages.ts
14+
const parent = require(__dirname + '/../import-packages.ts'); // $ importTarget=import-packages.ts
1515

1616
// Using __dirname with path concat and variable
1717
const subdir = 'nested';
18-
const dynamic = require(__dirname + '/' + subdir + '/target.js'); // $ MISSING: importTarget=DirnameImports/nested/target.js
18+
const dynamic = require(__dirname + '/' + subdir + '/target.js'); // $ importTarget=DirnameImports/nested/target.js
1919

2020
// Using __dirname in an AddExpr chain
21-
const chainedAdd = require(__dirname + '/' + 'target.js'); // $ MISSING: importTarget=DirnameImports/target.js
21+
const chainedAdd = require(__dirname + '/' + 'target.js'); // $ importTarget=DirnameImports/target.js

javascript/ql/test/library-tests/PathResolution/test.expected

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@
4040
| DeclarationFiles/src/main.ts:5:1:5:27 | import ... cript"; | DeclarationFiles/lib/typescript.ts |
4141
| DeclarationFiles/src/main.ts:6:1:6:30 | import ... pt.js"; | DeclarationFiles/lib/typescript.ts |
4242
| DeclarationFiles/src/main.ts:7:1:7:32 | import ... .d.ts"; | DeclarationFiles/lib/typescript.d.ts |
43+
| DirnameImports/main.js:5:16:5:48 | require ... et.js') | DirnameImports/target.js |
44+
| DirnameImports/main.js:8:22:8:63 | require ... t.js')) | DirnameImports/target.js |
45+
| DirnameImports/main.js:11:16:11:55 | require ... et.js') | DirnameImports/nested/target.js |
46+
| DirnameImports/main.js:14:16:14:60 | require ... es.ts') | import-packages.ts |
47+
| DirnameImports/main.js:18:17:18:64 | require ... et.js') | DirnameImports/nested/target.js |
48+
| DirnameImports/main.js:21:20:21:57 | require ... et.js') | DirnameImports/target.js |
4349
| Extended/src/main.ts:2:1:2:21 | import ... /file"; | Extended/lib/file.ts |
4450
| Extended/src/main.ts:3:1:3:24 | import ... le.ts"; | Extended/lib/file.ts |
4551
| Extended/src/main.ts:4:1:4:24 | import ... le.js"; | Extended/lib/file.ts |

0 commit comments

Comments
 (0)