Skip to content

Commit 3b1b3b4

Browse files
authored
Merge pull request github#12202 from github/redsun82/swift-codegen
> Out of curiosity: What is the end goal that we're trying to get to with this? Up until now we would be writing that predicate by hand, see [this example](https://github.com/github/codeql/blob/29c826000448c8382c9465896a2a3b71b1c96d90/swift/ql/lib/codeql/swift/elements/expr/MethodLookupExpr.qll#L29-L30). Now this will be given to us from the get go. For me this was prompted to give a nicer live demo later at my presentation 🙂
2 parents 29c8260 + e2d7a69 commit 3b1b3b4

File tree

7 files changed

+244
-152
lines changed

7 files changed

+244
-152
lines changed

swift/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,6 @@ compile_commands.json
1111
/.idea
1212
/cmake*
1313

14-
# VSCode default build directory
14+
# VSCode default build directory and project directory
1515
/build
16+
/.vscode

swift/codegen/generators/qlgen.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ def get_ql_property(cls: schema.Class, prop: schema.Property, prev_child: str =
146146
return ql.Property(**args)
147147

148148

149-
def get_ql_class(cls: schema.Class):
149+
def get_ql_class(cls: schema.Class) -> ql.Class:
150150
pragmas = {k: True for k in cls.pragmas if k.startswith("ql")}
151151
prev_child = ""
152152
properties = []
@@ -228,6 +228,10 @@ def format(codeql, files):
228228
log.debug(line.strip())
229229

230230

231+
def _get_path(cls: schema.Class) -> pathlib.Path:
232+
return pathlib.Path(cls.group or "", cls.name).with_suffix(".qll")
233+
234+
231235
def _get_all_properties(cls: schema.Class, lookup: typing.Dict[str, schema.Class],
232236
already_seen: typing.Optional[typing.Set[int]] = None) -> \
233237
typing.Iterable[typing.Tuple[schema.Class, schema.Property]]:
@@ -283,6 +287,29 @@ def _should_skip_qltest(cls: schema.Class, lookup: typing.Dict[str, schema.Class
283287
cls, lookup)
284288

285289

290+
def _get_stub(cls: schema.Class, base_import: str) -> ql.Stub:
291+
if isinstance(cls.ipa, schema.IpaInfo):
292+
if cls.ipa.from_class is not None:
293+
accessors = [
294+
ql.IpaUnderlyingAccessor(
295+
argument="Entity",
296+
type=_to_db_type(cls.ipa.from_class),
297+
constructorparams=["result"]
298+
)
299+
]
300+
elif cls.ipa.on_arguments is not None:
301+
accessors = [
302+
ql.IpaUnderlyingAccessor(
303+
argument=inflection.camelize(arg),
304+
type=_to_db_type(type),
305+
constructorparams=["result" if a == arg else "_" for a in cls.ipa.on_arguments]
306+
) for arg, type in cls.ipa.on_arguments.items()
307+
]
308+
else:
309+
accessors = []
310+
return ql.Stub(name=cls.name, base_import=base_import, ipa_accessors=accessors)
311+
312+
286313
def generate(opts, renderer):
287314
input = opts.schema
288315
out = opts.ql_output
@@ -323,10 +350,13 @@ def generate(opts, renderer):
323350
qll = out / c.path.with_suffix(".qll")
324351
c.imports = [imports[t] for t in get_classes_used_by(c)]
325352
renderer.render(c, qll)
326-
stub_file = stub_out / c.path.with_suffix(".qll")
353+
354+
for c in data.classes.values():
355+
path = _get_path(c)
356+
stub_file = stub_out / path
327357
if not renderer.is_customized_stub(stub_file):
328-
stub = ql.Stub(name=c.name, base_import=get_import(qll, opts.swift_dir))
329-
renderer.render(stub, stub_file)
358+
base_import = get_import(out / path, opts.swift_dir)
359+
renderer.render(_get_stub(c, base_import), stub_file)
330360

331361
# for example path/to/elements -> path/to/elements.qll
332362
renderer.render(ql.ImportList([i for name, i in imports.items() if not classes[name].ql_internal]),

swift/codegen/lib/ql.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,12 +134,29 @@ def has_doc(self) -> bool:
134134
return bool(self.doc) or self.ql_internal
135135

136136

137+
@dataclass
138+
class IpaUnderlyingAccessor:
139+
argument: str
140+
type: str
141+
constructorparams: List[Param]
142+
143+
def __post_init__(self):
144+
if self.constructorparams:
145+
self.constructorparams = [Param(x) for x in self.constructorparams]
146+
self.constructorparams[0].first = True
147+
148+
137149
@dataclass
138150
class Stub:
139151
template: ClassVar = 'ql_stub'
140152

141153
name: str
142154
base_import: str
155+
ipa_accessors: List[IpaUnderlyingAccessor] = field(default_factory=list)
156+
157+
@property
158+
def has_ipa_accessors(self) -> bool:
159+
return bool(self.ipa_accessors)
143160

144161

145162
@dataclass
Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
11
// generated by {{generator}}, remove this comment if you wish to edit this file
22
private import {{base_import}}
3+
{{#has_ipa_accessors}}
4+
private import codeql.swift.generated.Raw
5+
private import codeql.swift.generated.Synth
6+
{{/has_ipa_accessors}}
37

48
{{#ql_internal}}
59
/**
610
* INTERNAL: Do not use.
711
*/
812
{{/ql_internal}}
9-
class {{name}} extends Generated::{{name}} {}
13+
class {{name}} extends Generated::{{name}} {
14+
{{#ipa_accessors}}
15+
private
16+
cached {{type}} getUnderlying{{argument}}() { this = Synth::T{{name}}({{#constructorparams}}{{^first}},{{/first}}{{param}}{{/constructorparams}})}
17+
{{/ipa_accessors}}
18+
}

swift/codegen/test/test_ql.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,5 +149,12 @@ def test_class_without_description():
149149
assert prop.has_description is False
150150

151151

152+
def test_ipa_accessor_has_first_constructor_param_marked():
153+
params = ["a", "b", "c"]
154+
x = ql.IpaUnderlyingAccessor("foo", "bar", params)
155+
assert x.constructorparams[0].first
156+
assert [p.param for p in x.constructorparams] == params
157+
158+
152159
if __name__ == '__main__':
153160
sys.exit(pytest.main([__file__] + sys.argv[1:]))

swift/codegen/test/test_qlgen.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -816,5 +816,29 @@ def test_property_on_class_with_default_doc_name(generate_classes):
816816
}
817817

818818

819+
def test_stub_on_class_with_ipa_from_class(generate_classes):
820+
assert generate_classes([
821+
schema.Class("MyObject", ipa=schema.IpaInfo(from_class="A")),
822+
]) == {
823+
"MyObject.qll": (ql.Stub(name="MyObject", base_import=gen_import_prefix + "MyObject", ipa_accessors=[
824+
ql.IpaUnderlyingAccessor(argument="Entity", type="Raw::A", constructorparams=["result"]),
825+
]),
826+
ql.Class(name="MyObject", final=True, ipa=True)),
827+
}
828+
829+
830+
def test_stub_on_class_with_ipa_on_arguments(generate_classes):
831+
assert generate_classes([
832+
schema.Class("MyObject", ipa=schema.IpaInfo(on_arguments={"base": "A", "index": "int", "label": "string"})),
833+
]) == {
834+
"MyObject.qll": (ql.Stub(name="MyObject", base_import=gen_import_prefix + "MyObject", ipa_accessors=[
835+
ql.IpaUnderlyingAccessor(argument="Base", type="Raw::A", constructorparams=["result", "_", "_"]),
836+
ql.IpaUnderlyingAccessor(argument="Index", type="int", constructorparams=["_", "result", "_"]),
837+
ql.IpaUnderlyingAccessor(argument="Label", type="string", constructorparams=["_", "_", "result"]),
838+
]),
839+
ql.Class(name="MyObject", final=True, ipa=True)),
840+
}
841+
842+
819843
if __name__ == '__main__':
820844
sys.exit(pytest.main([__file__] + sys.argv[1:]))

0 commit comments

Comments
 (0)