Skip to content

Commit 52a10c8

Browse files
committed
Merge branch 'main' into swift-to-string
2 parents f17afa8 + 57b9e6e commit 52a10c8

File tree

31 files changed

+570
-63
lines changed

31 files changed

+570
-63
lines changed

.codeqlmanifest.json

Lines changed: 0 additions & 30 deletions
This file was deleted.

.github/workflows/js-ml-tests.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ on:
1212
paths:
1313
- "javascript/ql/experimental/adaptivethreatmodeling/**"
1414
- .github/workflows/js-ml-tests.yml
15+
workflow_dispatch:
1516

1617
defaults:
1718
run:

codeql-workspace.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
provide:
2+
- "*/ql/src/qlpack.yml"
3+
- "*/ql/lib/qlpack.yml"
4+
- "*/ql/test/qlpack.yml"
5+
- "*/ql/examples/qlpack.yml"
6+
- "*/ql/consistency-queries/qlpack.yml"
7+
- "cpp/ql/test/query-tests/Security/CWE/CWE-190/semmle/tainted/qlpack.yml"
8+
- "go/ql/config/legacy-support/qlpack.yml"
9+
- "go/build/codeql-extractor-go/codeql-extractor.yml"
10+
- "javascript/ql/experimental/adaptivethreatmodeling/lib/qlpack.yml"
11+
# This pack is explicitly excluded from the workspace since most users
12+
# will want to use a version of this pack from the package cache. Internal
13+
# users can uncomment the following line and place a custom ML model
14+
# in the corresponding pack to test a custom ML model within their local
15+
# checkout.
16+
# - "javascript/ql/experimental/adaptivethreatmodeling/model/qlpack.yml"
17+
- "javascript/ql/experimental/adaptivethreatmodeling/modelbuilding/qlpack.yml"
18+
- "javascript/ql/experimental/adaptivethreatmodeling/src/qlpack.yml"
19+
- "csharp/ql/campaigns/Solorigate/lib/qlpack.yml"
20+
- "csharp/ql/campaigns/Solorigate/src/qlpack.yml"
21+
- "csharp/ql/campaigns/Solorigate/test/qlpack.yml"
22+
- "misc/legacy-support/*/qlpack.yml"
23+
- "misc/suite-helpers/qlpack.yml"
24+
- "ruby/extractor-pack/codeql-extractor.yml"
25+
- "swift/extractor-pack/codeql-extractor.yml"
26+
- "ql/extractor-pack/codeql-extractor.ym"
27+
28+
versionPolicies:
29+
default:
30+
requireChangeNotes: true
31+
committedPrereleaseSuffix: dev
32+
committedVersion: nextPatchRelease

java/kotlin-extractor/src/main/java/com/semmle/extractor/java/OdasaOutput.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,14 @@
44
import java.io.File;
55
import java.io.IOException;
66
import java.util.Arrays;
7+
import java.util.Enumeration;
8+
import java.util.HashMap;
79
import java.util.LinkedHashMap;
810
import java.util.Map;
911
import java.util.Objects;
1012
import java.util.regex.Pattern;
13+
import java.util.zip.ZipEntry;
14+
import java.util.zip.ZipFile;
1115

1216
import com.github.codeql.Logger;
1317
import static com.github.codeql.ClassNamesKt.getIrDeclBinaryName;
@@ -547,6 +551,51 @@ else if (majorVersion==0)
547551
(tcv.majorVersion == majorVersion && tcv.minorVersion == minorVersion &&
548552
tcv.lastModified < lastModified);
549553
}
554+
555+
private static Map<String, Map<String, Long>> jarFileEntryTimeStamps = new HashMap<>();
556+
557+
private static Map<String, Long> getZipFileEntryTimeStamps(String path, Logger log) {
558+
try {
559+
Map<String, Long> result = new HashMap<>();
560+
ZipFile zf = new ZipFile(path);
561+
Enumeration<? extends ZipEntry> entries = zf.entries();
562+
while (entries.hasMoreElements()) {
563+
ZipEntry ze = entries.nextElement();
564+
result.put(ze.getName(), ze.getLastModifiedTime().toMillis());
565+
}
566+
return result;
567+
} catch(IOException e) {
568+
log.warn("Failed to get entry timestamps from " + path, e);
569+
return null;
570+
}
571+
}
572+
573+
private static long getVirtualFileTimeStamp(VirtualFile vf, Logger log) {
574+
if (vf.getFileSystem().getProtocol().equals("jar")) {
575+
String[] parts = vf.getPath().split("!/");
576+
if (parts.length == 2) {
577+
String jarFilePath = parts[0];
578+
String entryPath = parts[1];
579+
if (!jarFileEntryTimeStamps.containsKey(jarFilePath)) {
580+
jarFileEntryTimeStamps.put(jarFilePath, getZipFileEntryTimeStamps(jarFilePath, log));
581+
}
582+
Map<String, Long> entryTimeStamps = jarFileEntryTimeStamps.get(jarFilePath);
583+
if (entryTimeStamps != null) {
584+
Long entryTimeStamp = entryTimeStamps.get(entryPath);
585+
if (entryTimeStamp != null)
586+
return entryTimeStamp;
587+
else
588+
log.warn("Couldn't find timestamp for jar file " + jarFilePath + " entry " + entryPath);
589+
}
590+
} else {
591+
log.warn("Expected JAR-file path " + vf.getPath() + " to have exactly one '!/' separator");
592+
}
593+
}
594+
595+
// For all files except for jar files, and a fallback in case of I/O problems reading a jar file:
596+
return vf.getTimeStamp();
597+
}
598+
550599
private static TrapClassVersion fromSymbol(IrDeclaration sym, Logger log) {
551600
VirtualFile vf = sym instanceof IrClass ? getIrClassVirtualFile((IrClass)sym) :
552601
sym.getParent() instanceof IrClass ? getIrClassVirtualFile((IrClass)sym.getParent()) :
@@ -583,7 +632,7 @@ public void visit(int version, int access, java.lang.String name, java.lang.Stri
583632
};
584633
(new ClassReader(vf.contentsToByteArray())).accept(versionGetter, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
585634

586-
return new TrapClassVersion(versionStore[0] & 0xffff, versionStore[0] >> 16, vf.getTimeStamp(), "kotlin");
635+
return new TrapClassVersion(versionStore[0] & 0xffff, versionStore[0] >> 16, getVirtualFileTimeStamp(vf, log), "kotlin");
587636
}
588637
catch(IllegalAccessException e) {
589638
log.warn("Failed to read class file version information", e);

ruby/ql/consistency-queries/DataFlowConsistency.ql

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,7 @@ private class MyConsistencyConfiguration extends ConsistencyConfiguration {
99
n instanceof BlockArgumentNode
1010
or
1111
n instanceof SummaryNode
12+
or
13+
n instanceof HashSplatArgumentsNode
1214
}
1315
}

ruby/ql/lib/codeql/ruby/ast/Parameter.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ class NamedParameter extends Parameter, TNamedParameter {
109109
final VariableAccess getDefiningAccess() {
110110
result = this.getVariable().getDefiningAccess()
111111
or
112-
result = this.(SimpleParameterSynthImpl).getDefininingAccess()
112+
result = this.(SimpleParameterSynthImpl).getDefiningAccess()
113113
}
114114

115115
override AstNode getAChild(string pred) {

ruby/ql/lib/codeql/ruby/ast/Variable.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class VariableAccess extends Expr instanceof VariableAccessImpl {
116116
predicate isImplicitWrite() {
117117
implicitWriteAccess(toGenerated(this))
118118
or
119-
this = any(SimpleParameterSynthImpl p).getDefininingAccess()
119+
this = any(SimpleParameterSynthImpl p).getDefiningAccess()
120120
or
121121
this = any(HashPattern p).getValue(_)
122122
or

ruby/ql/lib/codeql/ruby/ast/internal/Parameter.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class SimpleParameterRealImpl extends SimpleParameterImpl, TSimpleParameterReal
3838
class SimpleParameterSynthImpl extends SimpleParameterImpl, TSimpleParameterSynth {
3939
SimpleParameterSynthImpl() { this = TSimpleParameterSynth(_, _) }
4040

41-
LocalVariableAccessSynth getDefininingAccess() { synthChild(this, 0, result) }
41+
LocalVariableAccessSynth getDefiningAccess() { synthChild(this, 0, result) }
4242

4343
override LocalVariable getVariableImpl() { result = TLocalVariableSynth(this, _) }
4444

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowDispatch.qll

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,8 @@ private module Cached {
259259
exists(any(Call c).getKeywordArgument(name))
260260
or
261261
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordParameterPosition(_, name)
262-
}
262+
} or
263+
THashSplatArgumentPosition()
263264

264265
cached
265266
newtype TParameterPosition =
@@ -278,6 +279,7 @@ private module Cached {
278279
or
279280
FlowSummaryImplSpecific::ParsePositions::isParsedKeywordArgumentPosition(_, name)
280281
} or
282+
THashSplatParameterPosition() or
281283
TAnyParameterPosition()
282284
}
283285

@@ -476,6 +478,9 @@ class ParameterPosition extends TParameterPosition {
476478
/** Holds if this position represents a keyword parameter named `name`. */
477479
predicate isKeyword(string name) { this = TKeywordParameterPosition(name) }
478480

481+
/** Holds if this position represents a hash-splat parameter. */
482+
predicate isHashSplat() { this = THashSplatParameterPosition() }
483+
479484
/**
480485
* Holds if this position represents any parameter. This includes both positional
481486
* and named parameters.
@@ -494,6 +499,8 @@ class ParameterPosition extends TParameterPosition {
494499
or
495500
exists(string name | this.isKeyword(name) and result = "keyword " + name)
496501
or
502+
this.isHashSplat() and result = "**"
503+
or
497504
this.isAny() and result = "any"
498505
}
499506
}
@@ -512,6 +519,12 @@ class ArgumentPosition extends TArgumentPosition {
512519
/** Holds if this position represents a keyword argument named `name`. */
513520
predicate isKeyword(string name) { this = TKeywordArgumentPosition(name) }
514521

522+
/**
523+
* Holds if this position represents a synthesized argument containing all keyword
524+
* arguments wrapped in a hash.
525+
*/
526+
predicate isHashSplat() { this = THashSplatArgumentPosition() }
527+
515528
/** Gets a textual representation of this position. */
516529
string toString() {
517530
this.isSelf() and result = "self"
@@ -521,6 +534,8 @@ class ArgumentPosition extends TArgumentPosition {
521534
exists(int pos | this.isPositional(pos) and result = "position " + pos)
522535
or
523536
exists(string name | this.isKeyword(name) and result = "keyword " + name)
537+
or
538+
this.isHashSplat() and result = "**"
524539
}
525540
}
526541

@@ -539,5 +554,7 @@ predicate parameterMatch(ParameterPosition ppos, ArgumentPosition apos) {
539554
or
540555
exists(string name | ppos.isKeyword(name) and apos.isKeyword(name))
541556
or
557+
ppos.isHashSplat() and apos.isHashSplat()
558+
or
542559
ppos.isAny() and exists(apos)
543560
}

ruby/ql/lib/codeql/ruby/dataflow/internal/DataFlowPrivate.qll

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ private class Argument extends CfgNodes::ExprCfgNode {
179179
this = call.getArgument(i) and
180180
not this.getExpr() instanceof BlockArgument and
181181
not this.getExpr().(Pair).getKey().getConstantValue().isSymbol(_) and
182+
not this.getExpr() instanceof HashSplatExpr and
182183
arg.isPositional(i)
183184
)
184185
or
@@ -189,6 +190,10 @@ private class Argument extends CfgNodes::ExprCfgNode {
189190
)
190191
or
191192
this = call.getReceiver() and arg.isSelf()
193+
or
194+
this = call.getAnArgument() and
195+
this.getExpr() instanceof HashSplatExpr and
196+
arg.isHashSplat()
192197
}
193198

194199
/** Holds if this expression is the `i`th argument of `c`. */
@@ -216,7 +221,8 @@ private module Cached {
216221
TNormalParameterNode(Parameter p) {
217222
p instanceof SimpleParameter or
218223
p instanceof OptionalParameter or
219-
p instanceof KeywordParameter
224+
p instanceof KeywordParameter or
225+
p instanceof HashSplatParameter
220226
} or
221227
TSelfParameterNode(MethodBase m) or
222228
TBlockParameterNode(MethodBase m) or
@@ -232,6 +238,9 @@ private module Cached {
232238
} or
233239
TSummaryParameterNode(FlowSummaryImpl::Public::SummarizedCallable c, ParameterPosition pos) {
234240
FlowSummaryImpl::Private::summaryParameterNodeRange(c, pos)
241+
} or
242+
THashSplatArgumentsNode(CfgNodes::ExprNodes::CallCfgNode c) {
243+
exists(Argument arg | arg.isArgumentOf(c, any(ArgumentPosition pos | pos.isKeyword(_))))
235244
}
236245

237246
class TParameterNode =
@@ -389,6 +398,8 @@ predicate nodeIsHidden(Node n) {
389398
n instanceof SummaryParameterNode
390399
or
391400
n instanceof SynthReturnNode
401+
or
402+
n instanceof HashSplatArgumentsNode
392403
}
393404

394405
/** An SSA definition, viewed as a node in a data flow graph. */
@@ -473,6 +484,9 @@ private module ParameterNodes {
473484
c.getAParameter() = kp and
474485
pos.isKeyword(kp.getName())
475486
)
487+
or
488+
parameter = c.getAParameter().(HashSplatParameter) and
489+
pos.isHashSplat()
476490
}
477491

478492
override CfgScope getCfgScope() { result = parameter.getCallable() }
@@ -651,6 +665,40 @@ private module ArgumentNodes {
651665
FlowSummaryImpl::Private::summaryArgumentNode(call, this, pos)
652666
}
653667
}
668+
669+
/**
670+
* A data-flow node that represents all keyword arguments wrapped in a hash.
671+
*
672+
* The callee is responsible for filtering out the keyword arguments that are
673+
* part of the method signature, such that those cannot end up in the hash-splat
674+
* parameter.
675+
*/
676+
class HashSplatArgumentsNode extends ArgumentNode, THashSplatArgumentsNode {
677+
CfgNodes::ExprNodes::CallCfgNode c;
678+
679+
HashSplatArgumentsNode() { this = THashSplatArgumentsNode(c) }
680+
681+
override predicate argumentOf(DataFlowCall call, ArgumentPosition pos) {
682+
this.sourceArgumentOf(call.asCall(), pos)
683+
}
684+
685+
override predicate sourceArgumentOf(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition pos) {
686+
call = c and
687+
pos.isHashSplat()
688+
}
689+
}
690+
691+
private class HashSplatArgumentsNodeImpl extends NodeImpl, THashSplatArgumentsNode {
692+
CfgNodes::ExprNodes::CallCfgNode c;
693+
694+
HashSplatArgumentsNodeImpl() { this = THashSplatArgumentsNode(c) }
695+
696+
override CfgScope getCfgScope() { result = c.getExpr().getCfgScope() }
697+
698+
override Location getLocationImpl() { result = c.getLocation() }
699+
700+
override string toStringImpl() { result = "**" }
701+
}
654702
}
655703

656704
import ArgumentNodes
@@ -807,6 +855,13 @@ predicate jumpStep(Node pred, Node succ) {
807855
succ.asExpr().getExpr().(ConstantReadAccess).getValue() = pred.asExpr().getExpr()
808856
}
809857

858+
private ContentSet getKeywordContent(string name) {
859+
exists(ConstantValue::ConstantSymbolValue key |
860+
result.isSingleton(TKnownElementContent(key)) and
861+
key.isSymbol(name)
862+
)
863+
}
864+
810865
/**
811866
* Holds if data can flow from `node1` to `node2` via an assignment to
812867
* content `c`.
@@ -845,6 +900,14 @@ predicate storeStep(Node node1, ContentSet c, Node node2) {
845900
c.isSingleton(TUnknownPairValueContent())
846901
)
847902
)
903+
or
904+
// Wrap all keyword arguments in a synthesized hash-splat argument node
905+
exists(CfgNodes::ExprNodes::CallCfgNode call, ArgumentPosition keywordPos, string name |
906+
node2 = THashSplatArgumentsNode(call) and
907+
node1.asExpr().(Argument).isArgumentOf(call, keywordPos) and
908+
keywordPos.isKeyword(name) and
909+
c = getKeywordContent(name)
910+
)
848911
}
849912

850913
/**
@@ -870,6 +933,19 @@ predicate readStep(Node node1, ContentSet c, Node node2) {
870933
*/
871934
predicate clearsContent(Node n, ContentSet c) {
872935
FlowSummaryImpl::Private::Steps::summaryClearsContent(n, c)
936+
or
937+
// Filter out keyword arguments that are part of the method signature from
938+
// the hash-splat parameter
939+
exists(
940+
DataFlowCallable callable, ParameterPosition hashSplatPos, ParameterNodeImpl keywordParam,
941+
ParameterPosition keywordPos, string name
942+
|
943+
n.(ParameterNodes::NormalParameterNode).isParameterOf(callable, hashSplatPos) and
944+
hashSplatPos.isHashSplat() and
945+
keywordParam.isParameterOf(callable, keywordPos) and
946+
keywordPos.isKeyword(name) and
947+
c = getKeywordContent(name)
948+
)
873949
}
874950

875951
/**

0 commit comments

Comments
 (0)