Skip to content

Commit dcbb66d

Browse files
committed
Go: extract and expose struct tags, interface method IDs
This enables us to distinguish all database types in QL. Previously structs with the same field names and types but differing tags, and interface types with matching method names and at least one non-exported method but declared in differing packages, were impossible or only sometimes possible to distinguish in QL. With this change these types can be distinguished, as well as permitting queries to examine struct field tags, e.g. to read JSON field name associations.
1 parent d0ca39f commit dcbb66d

30 files changed

+2359
-3
lines changed

go/downgrades/e47462df302b3e58a60d2e21f99aba63f973326f/go.dbscheme

Lines changed: 546 additions & 0 deletions
Large diffs are not rendered by default.

go/downgrades/e47462df302b3e58a60d2e21f99aba63f973326f/old.dbscheme

Lines changed: 552 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
description: Remove component-tags and interface-method-id tables
2+
compatibility: full
3+
4+
component_tags.rel: delete
5+
interface_private_method_ids.rel: delete

go/extractor/dbscheme/tables.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,6 +1150,20 @@ var ComponentTypesTable = NewTable("component_types",
11501150
EntityColumn(TypeType, "tp"),
11511151
).KeySet("parent", "index")
11521152

1153+
// ComponentTagsTable is the table associating composite types with their component types' tags
1154+
var ComponentTagsTable = NewTable("component_tags",
1155+
EntityColumn(CompositeType, "parent"),
1156+
IntColumn("index"),
1157+
StringColumn("tag"),
1158+
).KeySet("parent", "index")
1159+
1160+
// InterfacePrivateMethodIdsTable is the table associating interface types with their private method ids
1161+
var InterfacePrivateMethodIdsTable = NewTable("interface_private_method_ids",
1162+
EntityColumn(InterfaceType, "interface"),
1163+
IntColumn("index"),
1164+
StringColumn("id"),
1165+
).KeySet("interface", "index")
1166+
11531167
// ArrayLengthTable is the table associating array types with their length (represented as a string
11541168
// since Go array lengths are 64-bit and hence do not always fit into a QL integer)
11551169
var ArrayLengthTable = NewTable("array_length",

go/extractor/extractor.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,7 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
16241624
name = ""
16251625
}
16261626
extractComponentType(tw, lbl, i, name, field.Type())
1627+
dbscheme.ComponentTagsTable.Emit(tw, lbl, i, tp.Tag(i))
16271628
}
16281629
case *types.Pointer:
16291630
kind = dbscheme.PointerType.Index()
@@ -1641,6 +1642,13 @@ func extractType(tw *trap.Writer, tp types.Type) trap.Label {
16411642
extractMethod(tw, meth)
16421643

16431644
extractComponentType(tw, lbl, i, meth.Name(), meth.Type())
1645+
1646+
// meth.Id() will be equal to meth.Name() for an exported method, or
1647+
// packge-qualified otherwise.
1648+
privateMethodId := meth.Id()
1649+
if privateMethodId != meth.Name() {
1650+
dbscheme.InterfacePrivateMethodIdsTable.Emit(tw, lbl, i, privateMethodId)
1651+
}
16441652
}
16451653
for i := 0; i < tp.NumEmbeddeds(); i++ {
16461654
component := tp.EmbeddedType(i)

go/ql/lib/go.dbscheme

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,12 @@ underlying_type(unique int named: @namedtype ref, int tp: @type ref);
207207
#keyset[parent, index]
208208
component_types(int parent: @compositetype ref, int index: int ref, string name: string ref, int tp: @type ref);
209209

210+
#keyset[parent, index]
211+
component_tags(int parent: @compositetype ref, int index: int ref, string tag: string ref);
212+
213+
#keyset[interface, index]
214+
interface_private_method_ids(int interface: @interfacetype ref, int index: int ref, string id: string ref);
215+
210216
array_length(unique int tp: @arraytype ref, string len: string ref);
211217

212218
type_objects(unique int tp: @type ref, int object: @object ref);

go/ql/lib/semmle/go/Scopes.qll

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,13 @@ class Field extends Variable {
385385
this = base.getField(f)
386386
)
387387
}
388+
389+
/**
390+
* Gets the tag associated with this field, or the empty string if this field has no tag.
391+
*/
392+
string getTag() {
393+
declaringType.hasOwnFieldWithTag(_, this.getName(), this.getType(), _, result)
394+
}
388395
}
389396

390397
/**

go/ql/lib/semmle/go/Types.qll

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,17 @@ class StructType extends @structtype, CompositeType {
457457
)
458458
}
459459

460+
/**
461+
* Holds if this struct contains a field `name` with type `tp` and tag `tag`;
462+
* `isEmbedded` is true if the field is embedded.
463+
*
464+
* Note that this predicate does not take promoted fields into account.
465+
*/
466+
predicate hasOwnFieldWithTag(int i, string name, Type tp, boolean isEmbedded, string tag) {
467+
this.hasOwnField(i, name, tp, isEmbedded) and
468+
component_tags(this, i, tag)
469+
}
470+
460471
/**
461472
* Get a field with the name `name`; `isEmbedded` is true if the field is embedded.
462473
*
@@ -575,10 +586,12 @@ class StructType extends @structtype, CompositeType {
575586
override string pp() {
576587
result =
577588
"struct { " +
578-
concat(int i, string name, Type tp |
579-
component_types(this, i, name, tp)
589+
concat(int i, string name, Type tp, string tag, string tagToPrint |
590+
component_types(this, i, name, tp) and
591+
component_tags(this, i, tag) and
592+
(if tag = "" then tagToPrint = "" else tagToPrint = " `" + tag + "`")
580593
|
581-
name + " " + tp.pp(), "; " order by i
594+
name + " " + tp.pp() + tagToPrint, "; " order by i
582595
) + " }"
583596
}
584597

@@ -747,6 +760,31 @@ class InterfaceType extends @interfacetype, CompositeType {
747760
exists(int i | i >= 0 | component_types(this, i, name, result))
748761
}
749762

763+
/**
764+
* Gets the type of method `id` of this interface type.
765+
*
766+
* This differs from `getMethodType` in that if the method is not exported, the `id`
767+
* will be package-qualified. This means that the set of `id`s` together with any
768+
* embedded types fully distinguishes the interface from any other, whereas the set
769+
* of names matched by `getMethodName` may be ambiguous between interfaces with matching
770+
* exported methods and unexported methods that have matching names but belong to
771+
* different packages.
772+
*
773+
* For example, `interface { Exported() int; notExported() int }` declared in two
774+
* different packages defines two distinct types, but they appear identical according to
775+
* `getMethodType`.
776+
*/
777+
Type getMethodTypeById(string id) {
778+
exists(int i, string name | i >= 0 |
779+
component_types(this, i, name, result) and
780+
(
781+
interface_private_method_ids(this, i, id)
782+
or
783+
name = id and not interface_private_method_ids(this, i, _)
784+
)
785+
)
786+
}
787+
750788
override predicate hasMethod(string m, SignatureType t) { t = this.getMethodType(m) }
751789

752790
/**
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
class StructType extends @structtype {
2+
string toString() { result = "struct type" }
3+
}
4+
5+
from StructType st, int index
6+
where component_types(st, index, _, _)
7+
select st, index, ""

0 commit comments

Comments
 (0)