Skip to content

Commit ccda490

Browse files
authored
Merge pull request #2566 from github/koesie10/library-version
Add library versions to data extensions editor
2 parents 2392d7c + 4158df1 commit ccda490

File tree

14 files changed

+382
-62
lines changed

14 files changed

+382
-62
lines changed

extensions/ql-vscode/src/data-extensions-editor/bqrs.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
ExternalApiUsage,
66
} from "./external-api-usage";
77
import { ModeledMethodType } from "./modeled-method";
8+
import { parseLibraryFilename } from "./library";
89

910
export function decodeBqrsToExternalApiUsages(
1011
chunk: DecodedBqrsChunk,
@@ -15,7 +16,8 @@ export function decodeBqrsToExternalApiUsages(
1516
const usage = tuple[0] as Call;
1617
const signature = tuple[1] as string;
1718
const supported = (tuple[2] as string) === "true";
18-
const library = tuple[4] as string;
19+
let library = tuple[4] as string;
20+
let libraryVersion: string | undefined = tuple[5] as string;
1921
const type = tuple[6] as ModeledMethodType;
2022
const classification = tuple[8] as CallClassification;
2123

@@ -37,9 +39,25 @@ export function decodeBqrsToExternalApiUsages(
3739
methodDeclaration.indexOf("("),
3840
);
3941

42+
// For Java, we'll always get back a .jar file, and the library version may be bad because not all library authors
43+
// properly specify the version. Therefore, we'll always try to parse the name and version from the library filename
44+
// for Java.
45+
if (library.endsWith(".jar") || libraryVersion === "") {
46+
const { name, version } = parseLibraryFilename(library);
47+
library = name;
48+
if (version) {
49+
libraryVersion = version;
50+
}
51+
}
52+
53+
if (libraryVersion === "") {
54+
libraryVersion = undefined;
55+
}
56+
4057
if (!methodsByApiName.has(signature)) {
4158
methodsByApiName.set(signature, {
4259
library,
60+
libraryVersion,
4361
signature,
4462
packageName,
4563
typeName,

extensions/ql-vscode/src/data-extensions-editor/external-api-usage.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ export type Usage = Call & {
1818
};
1919

2020
export interface MethodSignature {
21+
/**
22+
* Contains the version of the library if it can be determined by CodeQL, e.g. `4.2.2.2`
23+
*/
24+
libraryVersion?: string;
2125
/**
2226
* A unique signature that can be used to identify this external API usage.
2327
*
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { basename, extname } from "../common/path";
2+
3+
// From the semver package using
4+
// const { re, t } = require("semver/internal/re");
5+
// console.log(re[t.LOOSE]);
6+
// Modifications:
7+
// - Added version named group which does not capture the v prefix
8+
// - Removed the ^ and $ anchors
9+
// - Made the minor and patch versions optional
10+
// - Added a hyphen to the start of the version
11+
// - Added a dot as a valid separator between the version and the label
12+
// - Made the patch version optional even if a label is given
13+
// This will match any semver string at the end of a larger string
14+
const semverRegex =
15+
/-[v=\s]*(?<version>([0-9]+)(\.([0-9]+)(?:(\.([0-9]+))?(?:[-.]?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?)?)?)/g;
16+
17+
export interface Library {
18+
name: string;
19+
version?: string;
20+
}
21+
22+
export function parseLibraryFilename(filename: string): Library {
23+
let libraryName = basename(filename);
24+
const extension = extname(libraryName);
25+
libraryName = libraryName.slice(0, -extension.length);
26+
27+
let libraryVersion: string | undefined;
28+
29+
let match: RegExpMatchArray | null = null;
30+
31+
// Reset the regex
32+
semverRegex.lastIndex = 0;
33+
34+
// Find the last occurence of the regex within the library name
35+
// eslint-disable-next-line no-constant-condition
36+
while (true) {
37+
const currentMatch = semverRegex.exec(libraryName);
38+
if (currentMatch === null) {
39+
break;
40+
}
41+
42+
match = currentMatch;
43+
}
44+
45+
if (match?.groups) {
46+
libraryVersion = match.groups?.version;
47+
// Remove everything after the start of the match
48+
libraryName = libraryName.slice(0, match.index);
49+
}
50+
51+
// Remove any leading or trailing hyphens or dots
52+
libraryName = libraryName.replaceAll(/^[.-]+|[.-]+$/g, "");
53+
54+
return {
55+
name: libraryName,
56+
version: libraryVersion,
57+
};
58+
}

extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ where
3030
usage = aUsage(api) and
3131
type = supportedType(api) and
3232
classification = methodClassification(usage)
33-
select usage, apiName, supported.toString(), "supported", api.getFile().getBaseName(), "library",
34-
type, "type", classification, "classification"
33+
select usage, apiName, supported.toString(), "supported", api.dllName(), api.dllVersion(), type,
34+
"type", classification, "classification"
3535
`,
3636
frameworkModeQuery: `/**
3737
* @name Public methods
@@ -111,7 +111,7 @@ class CallableMethod extends DotNet::Declaration {
111111
bindingset[this]
112112
private string getSignature() {
113113
result =
114-
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" +
114+
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "#" + this.getName() + "(" +
115115
parameterQualifiedTypeNamesToString(this) + ")"
116116
}
117117
@@ -125,7 +125,23 @@ class CallableMethod extends DotNet::Declaration {
125125
* Gets the namespace and signature of this API.
126126
*/
127127
bindingset[this]
128-
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }
128+
string getApiName() { result = this.getNamespace() + "." + this.getSignature() }
129+
130+
private string getDllName() { result = this.getLocation().(Assembly).getName() }
131+
132+
private string getDllVersion() { result = this.getLocation().(Assembly).getVersion().toString() }
133+
134+
string dllName() {
135+
result = this.getDllName()
136+
or
137+
not exists(this.getDllName()) and result = this.getFile().getBaseName()
138+
}
139+
140+
string dllVersion() {
141+
result = this.getDllVersion()
142+
or
143+
not exists(this.getDllVersion()) and result = ""
144+
}
129145
130146
/** Gets a node that is an input to a call to this API. */
131147
private ArgumentNode getAnInput() {
@@ -195,7 +211,7 @@ string supportedType(CallableMethod method) {
195211
or
196212
method.isNeutral() and result = "neutral"
197213
or
198-
not method.isSupported() and result = "none"
214+
not method.isSupported() and result = ""
199215
}
200216
201217
string methodClassification(Call method) {

extensions/ql-vscode/src/data-extensions-editor/queries/java.ts

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ where
2727
usage = aUsage(externalApi) and
2828
type = supportedType(externalApi) and
2929
classification = methodClassification(usage)
30-
select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(), "library",
31-
type, "type", classification, "classification"
30+
select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(),
31+
externalApi.jarVersion(), type, "type", classification, "classification"
3232
`,
3333
frameworkModeQuery: `/**
3434
* @name Public methods
@@ -75,7 +75,7 @@ private predicate isUninteresting(Callable c) {
7575
/**
7676
* A callable method from either the Standard Library, a 3rd party library or from the source.
7777
*/
78-
class CallableMethod extends Method {
78+
class CallableMethod extends Callable {
7979
CallableMethod() { not isUninteresting(this) }
8080
8181
/**
@@ -91,6 +91,10 @@ class CallableMethod extends Method {
9191
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
9292
}
9393
94+
private string getJarVersion() {
95+
result = this.getCompilationUnit().getParentContainer*().(JarFile).getSpecificationVersion()
96+
}
97+
9498
/**
9599
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
96100
*/
@@ -100,6 +104,15 @@ class CallableMethod extends Method {
100104
not exists(this.getJarName()) and result = "rt.jar"
101105
}
102106
107+
/**
108+
* Gets the version of the JAR file containing this API. Empty if no version is found in the JAR.
109+
*/
110+
string jarVersion() {
111+
result = this.getJarVersion()
112+
or
113+
not exists(this.getJarVersion()) and result = ""
114+
}
115+
103116
/** Gets a node that is an input to a call to this API. */
104117
private DataFlow::Node getAnInput() {
105118
exists(Call call | call.getCallee().getSourceDeclaration() = this |
@@ -160,7 +173,7 @@ string supportedType(CallableMethod method) {
160173
or
161174
method.isNeutral() and result = "neutral"
162175
or
163-
not method.isSupported() and result = "none"
176+
not method.isSupported() and result = ""
164177
}
165178
166179
string methodClassification(Call method) {

extensions/ql-vscode/src/data-extensions-editor/queries/query.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type Query = {
88
* - supported: whether the external API is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
99
* - "supported": a string literal. This is required to make the query a valid problem query.
1010
* - libraryName: the name of the library that contains the external API. This is a string and usually the basename of a file.
11-
* - "library": a string literal. This is required to make the query a valid problem query.
11+
* - libraryVersion: the version of the library that contains the external API. This is a string and can be empty if the version cannot be determined.
1212
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
1313
* - "type": a string literal. This is required to make the query a valid problem query.
1414
* - classification: the classification of the use of the method, either "source", "test", "generated", or "unknown"
@@ -25,7 +25,7 @@ export type Query = {
2525
* - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
2626
* - "supported": a string literal. This is required to make the query a valid problem query.
2727
* - libraryName: an arbitrary string. This is required to make it match the structure of the application query.
28-
* - "library": a string literal. This is required to make the query a valid problem query.
28+
* - libraryVersion: an arbitrary string. This is required to make it match the structure of the application query.
2929
* - type: the modeled kind of the method, either "sink", "source", "summary", or "neutral"
3030
* - "type": a string literal. This is required to make the query a valid problem query.
3131
* - "unknown": a string literal. This is required to make it match the structure of the application query.

extensions/ql-vscode/src/data-extensions-editor/yaml.ts

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import Ajv from "ajv";
22

3-
import { basename, extname } from "../common/path";
43
import { ExternalApiUsage } from "./external-api-usage";
54
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
65
import {
@@ -122,29 +121,12 @@ export function createDataExtensionYamlsForFrameworkMode(
122121
};
123122
}
124123

125-
// From the semver package using
126-
// const { re, t } = require("semver/internal/re");
127-
// console.log(re[t.LOOSE]);
128-
// Modified to remove the ^ and $ anchors
129-
// This will match any semver string at the end of a larger string
130-
const semverRegex =
131-
/[v=\s]*([0-9]+)\.([0-9]+)\.([0-9]+)(?:-?((?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*)(?:\.(?:[0-9]+|\d*[a-zA-Z-][a-zA-Z0-9-]*))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?/;
132-
133124
export function createFilenameForLibrary(
134125
library: string,
135126
prefix = "models/",
136127
suffix = ".model",
137128
) {
138-
let libraryName = basename(library);
139-
const extension = extname(libraryName);
140-
libraryName = libraryName.slice(0, -extension.length);
141-
142-
const match = semverRegex.exec(libraryName);
143-
144-
if (match !== null) {
145-
// Remove everything after the start of the match
146-
libraryName = libraryName.slice(0, match.index);
147-
}
129+
let libraryName = library;
148130

149131
// Lowercase everything
150132
libraryName = libraryName.toLowerCase();

extensions/ql-vscode/src/stories/data-extensions-editor/DataExtensionsEditor.stories.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ DataExtensionsEditor.args = {
3333
},
3434
initialExternalApiUsages: [
3535
{
36-
library: "sql2o-1.6.0.jar",
36+
library: "sql2o",
37+
libraryVersion: "1.6.0",
3738
signature: "org.sql2o.Connection#createQuery(String)",
3839
packageName: "org.sql2o",
3940
typeName: "Connection",
@@ -54,7 +55,8 @@ DataExtensionsEditor.args = {
5455
}),
5556
},
5657
{
57-
library: "sql2o-1.6.0.jar",
58+
library: "sql2o",
59+
libraryVersion: "1.6.0",
5860
signature: "org.sql2o.Query#executeScalar(Class)",
5961
packageName: "org.sql2o",
6062
typeName: "Query",
@@ -75,7 +77,8 @@ DataExtensionsEditor.args = {
7577
}),
7678
},
7779
{
78-
library: "sql2o-1.6.0.jar",
80+
library: "sql2o",
81+
libraryVersion: "1.6.0",
7982
signature: "org.sql2o.Sql2o#open()",
8083
packageName: "org.sql2o",
8184
typeName: "Sql2o",
@@ -96,7 +99,7 @@ DataExtensionsEditor.args = {
9699
}),
97100
},
98101
{
99-
library: "rt.jar",
102+
library: "rt",
100103
signature: "java.io.PrintStream#println(String)",
101104
packageName: "java.io",
102105
typeName: "PrintStream",
@@ -130,7 +133,8 @@ DataExtensionsEditor.args = {
130133
],
131134
},
132135
{
133-
library: "spring-boot-3.0.2.jar",
136+
library: "spring-boot",
137+
libraryVersion: "3.0.2",
134138
signature:
135139
"org.springframework.boot.SpringApplication#run(Class,String[])",
136140
packageName: "org.springframework.boot",
@@ -152,7 +156,8 @@ DataExtensionsEditor.args = {
152156
}),
153157
},
154158
{
155-
library: "sql2o-1.6.0.jar",
159+
library: "sql2o",
160+
libraryVersion: "1.6.0",
156161
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
157162
packageName: "org.sql2o",
158163
typeName: "Sql2o",
@@ -173,7 +178,8 @@ DataExtensionsEditor.args = {
173178
}),
174179
},
175180
{
176-
library: "sql2o-1.6.0.jar",
181+
library: "sql2o",
182+
libraryVersion: "1.6.0",
177183
signature: "org.sql2o.Sql2o#Sql2o(String)",
178184
packageName: "org.sql2o",
179185
typeName: "Sql2o",

extensions/ql-vscode/src/view/data-extensions-editor/LibraryRow.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ const ButtonsContainer = styled.div`
6868

6969
type Props = {
7070
title: string;
71+
libraryVersion?: string;
7172
externalApiUsages: ExternalApiUsage[];
7273
modeledMethods: Record<string, ModeledMethod>;
7374
viewState: DataExtensionEditorViewState;
@@ -91,6 +92,7 @@ type Props = {
9192

9293
export const LibraryRow = ({
9394
title,
95+
libraryVersion,
9496
externalApiUsages,
9597
modeledMethods,
9698
viewState,
@@ -158,7 +160,10 @@ export const LibraryRow = ({
158160
<Codicon name="chevron-right" label="Expand" />
159161
)}
160162
<NameContainer>
161-
<DependencyName>{title}</DependencyName>
163+
<DependencyName>
164+
{title}
165+
{libraryVersion && <>@{libraryVersion}</>}
166+
</DependencyName>
162167
<ModeledPercentage>
163168
{percentFormatter.format(modeledPercentage / 100)} modeled
164169
</ModeledPercentage>

extensions/ql-vscode/src/view/data-extensions-editor/MethodRow.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,8 +271,10 @@ function UnmodelableMethodRow(props: Props) {
271271
function ExternalApiUsageName(props: { externalApiUsage: ExternalApiUsage }) {
272272
return (
273273
<span>
274-
{props.externalApiUsage.packageName}.{props.externalApiUsage.typeName}.
275-
{props.externalApiUsage.methodName}
274+
{props.externalApiUsage.packageName && (
275+
<>{props.externalApiUsage.packageName}.</>
276+
)}
277+
{props.externalApiUsage.typeName}.{props.externalApiUsage.methodName}
276278
{props.externalApiUsage.methodParameters}
277279
</span>
278280
);

0 commit comments

Comments
 (0)