-
Notifications
You must be signed in to change notification settings - Fork 11
/
VariantParser.java
395 lines (360 loc) · 14.7 KB
/
VariantParser.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
/*******************************************************************************
* Copyright (c) 2015, 2023 Ericsson
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.tracecompass.internal.ctf.core.event.metadata.tsdl.variant;
import static org.eclipse.tracecompass.internal.ctf.core.event.metadata.tsdl.TsdlUtils.childTypeError;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.tracecompass.ctf.core.CTFException;
import org.eclipse.tracecompass.ctf.core.event.metadata.DeclarationScope;
import org.eclipse.tracecompass.ctf.core.event.types.EnumDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.IDeclaration;
import org.eclipse.tracecompass.ctf.core.event.types.VariantDeclaration;
import org.eclipse.tracecompass.ctf.core.trace.CTFTrace;
import org.eclipse.tracecompass.ctf.parser.CTFParser;
import org.eclipse.tracecompass.internal.ctf.core.event.metadata.AbstractScopedCommonTreeParser;
import org.eclipse.tracecompass.internal.ctf.core.event.metadata.JsonStructureFieldMemberMetadataNode;
import org.eclipse.tracecompass.internal.ctf.core.event.metadata.JsonStructureFieldMetadataNode;
import org.eclipse.tracecompass.internal.ctf.core.event.metadata.ParseException;
import org.eclipse.tracecompass.internal.ctf.core.event.types.ICTFMetadataNode;
import org.eclipse.tracecompass.internal.ctf.core.utils.JsonMetadataStrings;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
/**
*
* A CTF variant is a selection between different types. A CTF variant must
* always be defined within the scope of a structure or within fields contained
* within a structure (defined recursively). A _tag_ enumeration field must
* appear in either the same static scope, prior to the variant field (in field
* declaration order), in an upper static scope, or in an upper dynamic scope
* (see [Static and dynamic scopes](#spec7.3.2)). The type selection is
* indicated by the mapping from the enumeration value to the string used as
* variant type selector. The field to use as tag is specified by the
* `tag_field`, specified between `< >` after the `variant` keyword for unnamed
* variants, and after _variant name_ for named variants. It is not required
* that each enumeration mapping appears as variant type tag field. It is also
* not required that each variant type tag appears as enumeration mapping.
* However, it is required that any enumeration mapping encountered within a
* stream has a matching variant type tag field. <br>
* The alignment of the variant is the alignment of the type as selected by the
* tag value for the specific instance of the variant. The size of the variant
* is the size as selected by the tag value for the specific instance of the
* variant. <br>
* The alignment of the type containing the variant is independent of the
* variant alignment. For instance, if a structure contains two fields, a 32-bit
* integer, aligned on 32 bits, and a variant, which contains two choices:
* either a 32-bit field, aligned on 32 bits, or a 64-bit field, aligned on 64
* bits, the alignment of the outmost structure will be 32-bit (the alignment of
* its largest field, disregarding the alignment of the variant). The alignment
* of the variant will depend on the selector: if the variant's 32-bit field is
* selected, its alignment will be 32-bit, or 64-bit otherwise. It is important
* to note that variants are specifically tailored for compactness in a stream.
* Therefore, the relative offsets of compound type fields can vary depending on
* the offset at which the compound type starts if it contains a variant that
* itself contains a type with alignment larger than the largest field contained
* within the compound type. This is caused by the fact that the compound type
* may contain the enumeration that select the variant's choice, and therefore
* the alignment to be applied to the compound type cannot be determined before
* encountering the enumeration. <br>
* Each variant type selector possess a field name, which is a unique identifier
* within the variant. The identifier is not allowed to use any [reserved
* keyword](#C.1.2). Replacing reserved keywords with underscore-prefixed field
* names is recommended. Fields starting with an underscore should have their
* leading underscore removed by the CTF trace readers. <br>
* A named variant declaration followed by its definition within a structure
* declaration: <br>
*
* <pre>
variant name {
field_type sel1;
field_type sel2;
field_type sel3;
// ...
};
struct {
enum : integer_type { sel1, sel2, sel3, <em>more</em> } tag_field;
// ...
variant name <tag_field> v;
}
* </pre>
*
* An unnamed variant definition within a structure is expressed by the
* following TSDL metadata: <br>
*
* <pre>
struct {
enum : integer_type { sel1, sel2, sel3, <em>more</em> } tag_field;
// ...
variant <tag_field> {
field_type sel1;
field_type sel2;
field_type sel3;
// ...
} v;
}
* </pre>
*
* Example of a named variant within a sequence that refers to a single tag
* field: <br>
*
* <pre>
variant example {
uint32_t a;
uint64_t b;
short c;
};
struct {
enum : uint2_t { a, b, c } choice;
unsigned int seqlen;
variant example <choice> v[seqlen];
}
* </pre>
*
* Example of an unnamed variant: <br>
*
* <pre>
struct {
enum : uint2_t { a, b, c, d } choice;
// Unrelated fields can be added between the variant and its tag
int32_t somevalue;
variant <choice> {
uint32_t a;
uint64_t b;
short c;
struct {
unsigned int field1;
uint64_t field2;
} d;
} s;
}
* </pre>
*
* Example of an unnamed variant within an array: <br>
*
* <pre>
struct {
enum : uint2_t { a, b, c } choice;
variant <choice> {
uint32_t a;
uint64_t b;
short c;
} v[10];
}
* </pre>
*
* <br>
* Example of a variant type definition within a structure, where the defined
* type is then declared within an array of structures. This variant refers to a
* tag located in an upper static scope. This example clearly shows that a
* variant type definition referring to the tag `x` uses the closest preceding
* field from the static scope of the type definition. <br>
*
* <pre>
struct {
enum : uint2_t { a, b, c, d } x;
// "x" refers to the preceding "x" enumeration in the
// static scope of the type definition.
typedef variant <x> {
uint32_t a;
uint64_t b;
short c;
} example_variant;
struct {
enum : int { x, y, z } x; // This enumeration is not used by "v".
// "v" uses the "enum : uint2_t { a, b, c, d }" tag.
example_variant v;
} a[10];
}
* </pre>
*
* @author Matthew Khouzam
* @author Efficios - Javadoc preamble
*
*
*/
public final class VariantParser extends AbstractScopedCommonTreeParser {
private static final String OPTIONS = "options"; //$NON-NLS-1$
private static final String SELECTOR_FIELD_LOCATION = "selector-field-location"; //$NON-NLS-1$
/**
* Parameter object with a trace and current scope
*
* @author Matthew Khouzam
*
*/
@NonNullByDefault
public static final class Param implements ICommonTreeParserParameter {
private final DeclarationScope fDeclarationScope;
private final CTFTrace fTrace;
/**
* Constructor
*
* @param trace
* the trace
* @param scope
* the current scope
*/
public Param(CTFTrace trace, DeclarationScope scope) {
fTrace = trace;
fDeclarationScope = scope;
}
}
/**
* The instance
*/
public static final VariantParser INSTANCE = new VariantParser();
private VariantParser() {
}
/**
* Parse the variant
*
* @param variant
* the variant AST node
* @param param
* the {@link Param} parameter object
* @return a populated {@link VariantDeclaration}
* @throws ParseException
* the AST is malformed
*/
@Override
public VariantDeclaration parse(ICTFMetadataNode variant, ICommonTreeParserParameter param) throws ParseException {
if (!(param instanceof Param)) {
throw new IllegalArgumentException("Param must be a " + Param.class.getCanonicalName()); //$NON-NLS-1$
}
final DeclarationScope scope = ((Param) param).fDeclarationScope;
VariantDeclaration variantDeclaration = null;
boolean hasName = false;
String variantName = null;
boolean hasBody = false;
ICTFMetadataNode variantBody = null;
boolean hasTag = false;
String variantTag = null;
if (variant instanceof JsonStructureFieldMemberMetadataNode) {
JsonObject fieldClass = ((JsonStructureFieldMemberMetadataNode) variant).getFieldClass().getAsJsonObject();
if (fieldClass.has(SELECTOR_FIELD_LOCATION)) {
JsonArray location = fieldClass.get(SELECTOR_FIELD_LOCATION).getAsJsonArray();
variantTag = location.get(location.size() - 1).getAsString();
hasTag = true;
}
if (fieldClass.has(OPTIONS)) {
JsonArray options = fieldClass.get(OPTIONS).getAsJsonArray();
List<JsonStructureFieldMemberMetadataNode> memberClasses = new ArrayList<>();
for (JsonElement opt : options) {
JsonStructureFieldMemberMetadataNode memberClass = Objects.requireNonNull(new Gson().fromJson(opt, JsonStructureFieldMemberMetadataNode.class));
memberClasses.add(memberClass);
}
JsonStructureFieldMetadataNode body = new JsonStructureFieldMetadataNode(variant, JsonMetadataStrings.STRUCTURE, null);
body.setMemberClasses(memberClasses);
try {
body.initialize();
} catch (CTFException e) {
throw new ParseException("initialization issue ", e); //$NON-NLS-1$
}
variantBody = variant;
hasBody = true;
}
} else {
List<ICTFMetadataNode> children = variant.getChildren();
for (ICTFMetadataNode child : children) {
String type = child.getType();
if (CTFParser.tokenNames[CTFParser.VARIANT_NAME].equals(type)) {
hasName = true;
ICTFMetadataNode variantNameIdentifier = child.getChild(0);
variantName = variantNameIdentifier.getText();
} else if (CTFParser.tokenNames[CTFParser.VARIANT_TAG].equals(type)) {
hasTag = true;
// id
ICTFMetadataNode variantTagIdentifier = child.getChild(0);
variantTag = variantTagIdentifier.getText();
} else if (CTFParser.tokenNames[CTFParser.VARIANT_BODY].equals(type)) {
hasBody = true;
variantBody = child;
} else {
throw childTypeError(child);
}
}
}
if (hasBody) {
/*
* If variant has a name, check if already defined in the current
* scope.
*/
if (hasName
&& (scope.lookupVariant(variantName) != null)) {
throw new ParseException("variant " + variantName //$NON-NLS-1$
+ " already defined."); //$NON-NLS-1$
}
/* Create the declaration */
variantDeclaration = new VariantDeclaration();
CTFTrace trace = ((Param) param).fTrace;
/* Parse the body */
VariantBodyParser.INSTANCE.parse(variantBody, new VariantBodyParser.Param(variantDeclaration, trace, variantName, scope));
/* If variant has name, add it to the current scope. */
if (hasName) {
scope.registerVariant(variantName, variantDeclaration);
}
} else /* !hasBody */ {
if (hasName) {
/* Name and !body */
/* Lookup the name in the current scope. */
variantDeclaration = scope.lookupVariantRecursive(variantName);
/*
* If not found, it means that a struct with such name has not
* been defined
*/
if (variantDeclaration == null) {
throw new ParseException("variant " + variantName //$NON-NLS-1$
+ " is not defined"); //$NON-NLS-1$
}
} else {
/* !Name and !body */
/* We can't do anything with that. */
throw new ParseException("variant with no name and no body"); //$NON-NLS-1$
}
}
if (hasTag) {
variantDeclaration.setTag(variantTag);
IDeclaration decl = scope.lookupIdentifierRecursive(variantTag);
if (decl == null) {
throw new ParseException("Variant tag not found: " + variantTag); //$NON-NLS-1$
}
if (!(decl instanceof EnumDeclaration)) {
throw new ParseException("Variant tag must be an enum: " + variantTag); //$NON-NLS-1$
}
EnumDeclaration tagDecl = (EnumDeclaration) decl;
if (!intersects(tagDecl.getLabels(), variantDeclaration.getFields().keySet())) {
throw new ParseException("Variant contains no values of the tag, impossible to use: " + variantName); //$NON-NLS-1$
}
}
return variantDeclaration;
}
/**
* Method to compute if the two sets intersect. Faster than computing the
* intersection of a set as we break as soon as a common element is found.
*
* @param set
* first set
* @param fasterSet
* second set, with faster lookups
* @return true if a string is in both the set and the map's keySet
*/
private static boolean intersects(Set<String> set, Set<String> fasterSet) {
for (String setString : set) {
if (fasterSet.contains(setString)) {
return true;
}
}
return false;
}
}