@openzeppelin/upgrades-core 1.20.5 → 1.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/artifacts/@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol/ERC1967Proxy.dbg.json +1 -1
  2. package/artifacts/@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol/BeaconProxy.dbg.json +1 -1
  3. package/artifacts/@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol/UpgradeableBeacon.dbg.json +1 -1
  4. package/artifacts/build-info.json +151 -34
  5. package/dist/error.d.ts +1 -1
  6. package/dist/error.d.ts.map +1 -1
  7. package/dist/levenshtein.d.ts +4 -4
  8. package/dist/levenshtein.d.ts.map +1 -1
  9. package/dist/manifest.js +3 -3
  10. package/dist/manifest.js.map +1 -1
  11. package/dist/provider.d.ts.map +1 -1
  12. package/dist/provider.js +2 -0
  13. package/dist/provider.js.map +1 -1
  14. package/dist/scripts/migrate-oz-cli-project.d.ts +1 -1
  15. package/dist/scripts/migrate-oz-cli-project.d.ts.map +1 -1
  16. package/dist/solc-api.d.ts +1 -1
  17. package/dist/solc-api.d.ts.map +1 -1
  18. package/dist/src-decoder.d.ts +1 -1
  19. package/dist/src-decoder.d.ts.map +1 -1
  20. package/dist/src-decoder.js +1 -1
  21. package/dist/src-decoder.js.map +1 -1
  22. package/dist/storage/compare.d.ts +7 -7
  23. package/dist/storage/compare.d.ts.map +1 -1
  24. package/dist/storage/layout.d.ts +4 -4
  25. package/dist/storage/layout.d.ts.map +1 -1
  26. package/dist/storage/report.js +8 -5
  27. package/dist/storage/report.js.map +1 -1
  28. package/dist/utils/deep-array.d.ts +1 -1
  29. package/dist/utils/deep-array.d.ts.map +1 -1
  30. package/dist/validate/compat.d.ts +4 -4
  31. package/dist/validate/compat.d.ts.map +1 -1
  32. package/dist/validate/data.d.ts +4 -4
  33. package/dist/validate/data.d.ts.map +1 -1
  34. package/dist/validate/query.d.ts.map +1 -1
  35. package/dist/validate/query.js +12 -8
  36. package/dist/validate/query.js.map +1 -1
  37. package/dist/validate/run.d.ts +7 -2
  38. package/dist/validate/run.d.ts.map +1 -1
  39. package/dist/validate/run.js +119 -26
  40. package/dist/validate/run.js.map +1 -1
  41. package/dist/validate/run.test.d.ts +2 -0
  42. package/dist/validate/run.test.d.ts.map +1 -0
  43. package/dist/validate/run.test.js +43 -0
  44. package/dist/validate/run.test.js.map +1 -0
  45. package/dist/validate-ignore-errors.test.d.ts +2 -0
  46. package/dist/validate-ignore-errors.test.d.ts.map +1 -0
  47. package/dist/validate-ignore-errors.test.js +123 -0
  48. package/dist/validate-ignore-errors.test.js.map +1 -0
  49. package/package.json +2 -2
  50. package/src/provider.ts +2 -0
  51. package/src/src-decoder.ts +1 -1
  52. package/src/storage/report.ts +9 -5
  53. package/src/validate/query.ts +15 -11
  54. package/src/validate/run.test.ts +49 -0
  55. package/src/validate/run.ts +190 -31
  56. package/src/validate-ignore-errors.test.ts +137 -0
@@ -1,6 +1,6 @@
1
1
  import { Node } from 'solidity-ast/node';
2
- import { isNodeType, findAll } from 'solidity-ast/utils';
3
- import type { ContractDefinition } from 'solidity-ast';
2
+ import { isNodeType, findAll, ASTDereferencer } from 'solidity-ast/utils';
3
+ import type { ContractDefinition, FunctionDefinition } from 'solidity-ast';
4
4
 
5
5
  import { SolcOutput, SolcBytecode } from '../solc-api';
6
6
  import { SrcDecoder } from '../src-decoder';
@@ -68,6 +68,26 @@ interface ValidationErrorOpcode extends ValidationErrorBase {
68
68
  kind: 'delegatecall' | 'selfdestruct';
69
69
  }
70
70
 
71
+ interface OpcodePattern {
72
+ kind: 'delegatecall' | 'selfdestruct';
73
+ pattern: RegExp;
74
+ }
75
+
76
+ const OPCODES = {
77
+ delegatecall: {
78
+ kind: 'delegatecall',
79
+ pattern: /^t_function_baredelegatecall_/,
80
+ },
81
+ selfdestruct: {
82
+ kind: 'selfdestruct',
83
+ pattern: /^t_function_selfdestruct_/,
84
+ },
85
+ } as const;
86
+
87
+ export function isOpcodeError(error: ValidationErrorBase): error is ValidationErrorOpcode {
88
+ return error.kind === 'delegatecall' || error.kind === 'selfdestruct';
89
+ }
90
+
71
91
  interface ValidationErrorUpgradeability extends ValidationErrorBase {
72
92
  kind: 'missing-public-upgradeto';
73
93
  }
@@ -84,34 +104,49 @@ function* execall(re: RegExp, text: string) {
84
104
  }
85
105
  }
86
106
 
87
- function getAllowed(node: Node): string[] {
107
+ function getAllowed(node: Node, reachable: boolean): string[] {
88
108
  if ('documentation' in node) {
109
+ const tag = `oz-upgrades-unsafe-allow${reachable ? '-reachable' : ''}`;
89
110
  const doc = typeof node.documentation === 'string' ? node.documentation : node.documentation?.text ?? '';
111
+ return getAnnotationArgs(doc, tag);
112
+ } else {
113
+ return [];
114
+ }
115
+ }
90
116
 
91
- const result: string[] = [];
92
- for (const { groups } of execall(
93
- /^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s@\w+)[^])*)/m,
94
- doc,
95
- )) {
96
- if (groups && groups.title === 'custom' && groups.tag === 'oz-upgrades-unsafe-allow') {
97
- result.push(...groups.args.split(/\s+/));
117
+ /**
118
+ * Get args from the doc string matching the given tag
119
+ */
120
+ export function getAnnotationArgs(doc: string, tag: string) {
121
+ const result: string[] = [];
122
+ for (const { groups } of execall(
123
+ /^\s*(?:@(?<title>\w+)(?::(?<tag>[a-z][a-z-]*))? )?(?<args>(?:(?!^\s*@\w+)[^])*)/m,
124
+ doc,
125
+ )) {
126
+ if (groups && groups.title === 'custom' && groups.tag === tag) {
127
+ const trimmedArgs = groups.args.trim();
128
+ if (trimmedArgs.length > 0) {
129
+ result.push(...trimmedArgs.split(/\s+/));
98
130
  }
99
131
  }
132
+ }
100
133
 
101
- result.forEach(arg => {
102
- if (!(errorKinds as readonly string[]).includes(arg)) {
103
- throw new Error(`NatSpec: oz-upgrades-unsafe-allow argument not recognized: ${arg}`);
104
- }
105
- });
134
+ result.forEach(arg => {
135
+ if (!(errorKinds as readonly string[]).includes(arg)) {
136
+ throw new Error(`NatSpec: ${tag} argument not recognized: ${arg}`);
137
+ }
138
+ });
106
139
 
107
- return result;
108
- } else {
109
- return [];
110
- }
140
+ return result;
141
+ }
142
+
143
+ function skipCheckReachable(error: string, node: Node): boolean {
144
+ return getAllowed(node, true).includes(error);
111
145
  }
112
146
 
113
147
  function skipCheck(error: string, node: Node): boolean {
114
- return getAllowed(node).includes(error);
148
+ // skip both allow and allow-reachable errors in the lexical scope
149
+ return getAllowed(node, false).includes(error) || getAllowed(node, true).includes(error);
115
150
  }
116
151
 
117
152
  function getFullyQualifiedName(source: string, contractName: string) {
@@ -163,7 +198,7 @@ export function validate(solcOutput: SolcOutput, decodeSrc: SrcDecoder, solcVers
163
198
  validation[key].src = decodeSrc(contractDef);
164
199
  validation[key].errors = [
165
200
  ...getConstructorErrors(contractDef, decodeSrc),
166
- ...getOpcodeErrors(contractDef, decodeSrc),
201
+ ...getOpcodeErrors(contractDef, deref, decodeSrc),
167
202
  ...getStateVariableErrors(contractDef, decodeSrc),
168
203
  // TODO: add linked libraries support
169
204
  // https://github.com/OpenZeppelin/openzeppelin-upgrades/issues/52
@@ -206,27 +241,151 @@ function* getConstructorErrors(contractDef: ContractDefinition, decodeSrc: SrcDe
206
241
  }
207
242
  }
208
243
 
209
- function* getOpcodeErrors(contractDef: ContractDefinition, decodeSrc: SrcDecoder): Generator<ValidationErrorOpcode> {
210
- for (const fnCall of findAll('FunctionCall', contractDef, node => skipCheck('delegatecall', node))) {
244
+ function* getOpcodeErrors(
245
+ contractDef: ContractDefinition,
246
+ deref: ASTDereferencer,
247
+ decodeSrc: SrcDecoder,
248
+ ): Generator<ValidationErrorOpcode> {
249
+ yield* getContractOpcodeErrors(contractDef, deref, decodeSrc, OPCODES.delegatecall, 'main');
250
+ yield* getContractOpcodeErrors(contractDef, deref, decodeSrc, OPCODES.selfdestruct, 'main');
251
+ }
252
+
253
+ /**
254
+ * Whether this node is being visited as part of a main contract, or as an inherited contract or function
255
+ */
256
+ type Scope = 'main' | 'inherited';
257
+
258
+ function* getContractOpcodeErrors(
259
+ contractDef: ContractDefinition,
260
+ deref: ASTDereferencer,
261
+ decodeSrc: SrcDecoder,
262
+ opcode: OpcodePattern,
263
+ scope: Scope,
264
+ visitedNodeIds = new Map<number, Scope>(),
265
+ ): Generator<ValidationErrorOpcode> {
266
+ if (wasVisited(contractDef.id, scope, visitedNodeIds)) {
267
+ // We return early here, but the errors from this node were emitted when the node was marked as visited.
268
+ return;
269
+ } else {
270
+ visitedNodeIds.set(contractDef.id, scope);
271
+ }
272
+
273
+ yield* getFunctionOpcodeErrors(contractDef, deref, decodeSrc, opcode, scope, visitedNodeIds);
274
+ yield* getInheritedContractOpcodeErrors(contractDef, deref, decodeSrc, opcode, visitedNodeIds);
275
+ }
276
+
277
+ function wasVisited(key: number, scope: Scope, visitedNodeIds: Map<number, Scope>) {
278
+ return (
279
+ (scope === 'inherited' && visitedNodeIds.has(key)) || // looking up an inherited contract, and it was previously checked as a main contract OR inherited contract
280
+ (scope === 'main' && visitedNodeIds.get(key) === 'main') // looking up a main contract, and it was previously checked as a main contract
281
+ );
282
+ }
283
+
284
+ function* getFunctionOpcodeErrors(
285
+ contractOrFunctionDef: ContractDefinition | FunctionDefinition,
286
+ deref: ASTDereferencer,
287
+ decodeSrc: SrcDecoder,
288
+ opcode: OpcodePattern,
289
+ scope: Scope,
290
+ visitedNodeIds: Map<number, Scope>,
291
+ ): Generator<ValidationErrorOpcode> {
292
+ const parentContractDef = getParentDefinition(deref, contractOrFunctionDef);
293
+ if (parentContractDef === undefined || !skipCheck(opcode.kind, parentContractDef)) {
294
+ yield* getDirectFunctionOpcodeErrors(contractOrFunctionDef, decodeSrc, opcode, scope);
295
+ }
296
+ if (parentContractDef === undefined || !skipCheckReachable(opcode.kind, parentContractDef)) {
297
+ yield* getReferencedFunctionOpcodeErrors(contractOrFunctionDef, deref, decodeSrc, opcode, scope, visitedNodeIds);
298
+ }
299
+ }
300
+
301
+ function* getDirectFunctionOpcodeErrors(
302
+ contractOrFunctionDef: ContractDefinition | FunctionDefinition,
303
+ decodeSrc: SrcDecoder,
304
+ opcode: OpcodePattern,
305
+ scope: Scope,
306
+ ) {
307
+ for (const fnCall of findAll(
308
+ 'FunctionCall',
309
+ contractOrFunctionDef,
310
+ node => skipCheck(opcode.kind, node) || (scope === 'inherited' && isInternalFunction(node)),
311
+ )) {
211
312
  const fn = fnCall.expression;
212
- if (fn.typeDescriptions.typeIdentifier?.match(/^t_function_baredelegatecall_/)) {
313
+ if (fn.typeDescriptions.typeIdentifier?.match(opcode.pattern)) {
213
314
  yield {
214
- kind: 'delegatecall',
315
+ kind: opcode.kind,
215
316
  src: decodeSrc(fnCall),
216
317
  };
217
318
  }
218
319
  }
219
- for (const fnCall of findAll('FunctionCall', contractDef, node => skipCheck('selfdestruct', node))) {
320
+ }
321
+
322
+ function* getReferencedFunctionOpcodeErrors(
323
+ contractOrFunctionDef: ContractDefinition | FunctionDefinition,
324
+ deref: ASTDereferencer,
325
+ decodeSrc: SrcDecoder,
326
+ opcode: OpcodePattern,
327
+ scope: Scope,
328
+ visitedNodeIds: Map<number, Scope>,
329
+ ) {
330
+ for (const fnCall of findAll(
331
+ 'FunctionCall',
332
+ contractOrFunctionDef,
333
+ node => skipCheckReachable(opcode.kind, node) || (scope === 'inherited' && isInternalFunction(node)),
334
+ )) {
220
335
  const fn = fnCall.expression;
221
- if (fn.typeDescriptions.typeIdentifier?.match(/^t_function_selfdestruct_/)) {
222
- yield {
223
- kind: 'selfdestruct',
224
- src: decodeSrc(fnCall),
225
- };
336
+ if ('referencedDeclaration' in fn && fn.referencedDeclaration && fn.referencedDeclaration > 0) {
337
+ // non-positive references refer to built-in functions
338
+ const referencedNode = tryDerefFunction(deref, fn.referencedDeclaration);
339
+ if (referencedNode !== undefined) {
340
+ if (!wasVisited(referencedNode.id, scope, visitedNodeIds)) {
341
+ visitedNodeIds.set(referencedNode.id, scope);
342
+ yield* getFunctionOpcodeErrors(referencedNode, deref, decodeSrc, opcode, scope, visitedNodeIds);
343
+ }
344
+ }
226
345
  }
227
346
  }
228
347
  }
229
348
 
349
+ function tryDerefFunction(deref: ASTDereferencer, referencedDeclaration: number): FunctionDefinition | undefined {
350
+ try {
351
+ return deref(['FunctionDefinition'], referencedDeclaration);
352
+ } catch (e: any) {
353
+ if (!e.message.includes('No node with id')) {
354
+ throw e;
355
+ }
356
+ }
357
+ }
358
+
359
+ function* getInheritedContractOpcodeErrors(
360
+ contractDef: ContractDefinition,
361
+ deref: ASTDereferencer,
362
+ decodeSrc: SrcDecoder,
363
+ opcode: OpcodePattern,
364
+ visitedNodeIds: Map<number, Scope>,
365
+ ) {
366
+ if (!skipCheckReachable(opcode.kind, contractDef)) {
367
+ for (const base of contractDef.baseContracts) {
368
+ const referencedContract = deref('ContractDefinition', base.baseName.referencedDeclaration);
369
+ yield* getContractOpcodeErrors(referencedContract, deref, decodeSrc, opcode, 'inherited', visitedNodeIds);
370
+ }
371
+ }
372
+ }
373
+
374
+ function getParentDefinition(deref: ASTDereferencer, contractOrFunctionDef: ContractDefinition | FunctionDefinition) {
375
+ const parentNode = deref(['ContractDefinition', 'SourceUnit'], contractOrFunctionDef.scope);
376
+ if (parentNode.nodeType === 'ContractDefinition') {
377
+ return parentNode;
378
+ }
379
+ }
380
+
381
+ function isInternalFunction(node: Node) {
382
+ return (
383
+ node.nodeType === 'FunctionDefinition' &&
384
+ node.kind !== 'constructor' && // do not consider constructors as internal, because they are always called by children contracts' constructors
385
+ (node.visibility === 'internal' || node.visibility === 'private')
386
+ );
387
+ }
388
+
230
389
  function* getStateVariableErrors(
231
390
  contractDef: ContractDefinition,
232
391
  decodeSrc: SrcDecoder,
@@ -0,0 +1,137 @@
1
+ import _test, { TestFn } from 'ava';
2
+ import { artifacts } from 'hardhat';
3
+
4
+ import { validate, getContractVersion, assertUpgradeSafe, ValidationOptions, RunValidation } from './validate';
5
+ import { solcInputOutputDecoder } from './src-decoder';
6
+
7
+ interface Context {
8
+ validation: RunValidation;
9
+ }
10
+
11
+ const test = _test as TestFn<Context>;
12
+
13
+ test.before(async t => {
14
+ const contracts = [
15
+ 'contracts/test/ignore-errors/SafeContract.sol:SafeContract',
16
+ 'contracts/test/ignore-errors/SafeContractWithFreeFunctionCall.sol:SafeContractWithFreeFunctionCall',
17
+ 'contracts/test/ignore-errors/SafeContractWithLibraryCall.sol:SafeContractWithLibraryCall',
18
+ 'contracts/test/ignore-errors/SafeContractWithLibraryImport.sol:SafeContractWithLibraryImport',
19
+ 'contracts/test/ignore-errors/SafeContractWithLibraryUsingFor.sol:SafeContractWithLibraryUsingFor',
20
+ 'contracts/test/ignore-errors/SafeContractWithTransitiveLibraryCall.sol:SafeContractWithTransitiveLibraryCall',
21
+ 'contracts/test/ignore-errors/SafeContractWithParentCall.sol:SafeContractWithParentCall',
22
+ 'contracts/test/ignore-errors/RiskyParentContract.sol:RiskyParentContract',
23
+ 'contracts/test/ignore-errors/UnsafeContract.sol:UnsafeContract',
24
+ 'contracts/test/ignore-errors/UnsafeContractWithFreeFunctionCall.sol:UnsafeContractWithFreeFunctionCall',
25
+ 'contracts/test/ignore-errors/UnsafeContractWithLibraryCall.sol:UnsafeContractWithLibraryCall',
26
+ 'contracts/test/ignore-errors/UnsafeContractWithLibraryUsingFor.sol:UnsafeContractWithLibraryUsingFor',
27
+ 'contracts/test/ignore-errors/UnsafeContractWithTransitiveLibraryCall.sol:UnsafeContractWithTransitiveLibraryCall',
28
+ 'contracts/test/ignore-errors/UnsafeContractWithParentCall.sol:UnsafeContractWithParentCall',
29
+ 'contracts/test/ignore-errors/UnsafeContractWithInheritedParent.sol:UnsafeContractWithInheritedParent',
30
+ 'contracts/test/ignore-errors/UnsafeContractWithInheritedTransitiveParent.sol:UnsafeContractWithInheritedTransitiveParent',
31
+ 'contracts/test/ignore-errors/UnsafeAllow.sol:UnsafeAllow',
32
+ 'contracts/test/ignore-errors/AllowReachable.sol:AllowReachable',
33
+ 'contracts/test/ignore-errors/UnsafeAllowReachableDifferentOpcode.sol:UnsafeAllowReachableDifferentOpcode',
34
+ 'contracts/test/ignore-errors/UnsafeAllowParent.sol:UnsafeAllowParent',
35
+ 'contracts/test/ignore-errors/AllowReachableParent.sol:AllowReachableParent',
36
+ 'contracts/test/ignore-errors/UnsafeAllowReachableParentDifferentOpcode.sol:UnsafeAllowReachableParentDifferentOpcode',
37
+ 'contracts/test/ignore-errors/AllowChildInheritedTransitive.sol:AllowChildInheritedTransitive',
38
+ 'contracts/test/ignore-errors/AllowChild.sol:AllowChild',
39
+ 'contracts/test/ignore-errors/AllowChildCallTransitive.sol:AllowChildCallTransitive',
40
+ 'contracts/test/ignore-errors/AllowChildSelfReachable.sol:AllowChildSelfReachable',
41
+ 'contracts/test/ignore-errors/SafeRecursion.sol:SafeRecursion',
42
+ 'contracts/test/ignore-errors/UnsafeRecursion.sol:UnsafeRecursion',
43
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeChild1',
44
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeChild2',
45
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeChild3',
46
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeChild4',
47
+ 'contracts/test/ignore-errors/Constructors.sol:AllowChild5',
48
+ 'contracts/test/ignore-errors/Constructors.sol:AllowChild6',
49
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeAllowChild7',
50
+ 'contracts/test/ignore-errors/Constructors.sol:AllowReachableChild8',
51
+ 'contracts/test/ignore-errors/Constructors.sol:UnsafeChild9',
52
+ // 'contracts/test/ignore-errors/Modifiers.sol:ModifierNotUsed',
53
+ 'contracts/test/ignore-errors/Modifiers.sol:ModifierUsed',
54
+ ];
55
+
56
+ t.context.validation = {} as RunValidation;
57
+ for (const contract of contracts) {
58
+ const buildInfo = await artifacts.getBuildInfo(contract);
59
+ if (buildInfo === undefined) {
60
+ throw new Error(`Build info not found for contract ${contract}`);
61
+ }
62
+ const solcOutput = buildInfo.output;
63
+ const solcInput = buildInfo.input;
64
+ const decodeSrc = solcInputOutputDecoder(solcInput, solcOutput);
65
+ Object.assign(t.context.validation, validate(solcOutput, decodeSrc));
66
+ }
67
+ });
68
+
69
+ function testValid(name: string, kind: ValidationOptions['kind'], valid: boolean) {
70
+ testOverride(name, kind, {}, valid);
71
+ }
72
+
73
+ function testOverride(name: string, kind: ValidationOptions['kind'], opts: ValidationOptions, valid: boolean) {
74
+ const optKeys = Object.keys(opts);
75
+ const describeOpts = optKeys.length > 0 ? '(' + optKeys.join(', ') + ')' : '';
76
+ const testName = [valid ? 'accepts' : 'rejects', kind, name, describeOpts].join(' ');
77
+ test(testName, t => {
78
+ const version = getContractVersion(t.context.validation, name);
79
+ const assertUpgSafe = () => assertUpgradeSafe([t.context.validation], version, { kind, ...opts });
80
+ if (valid) {
81
+ t.notThrows(assertUpgSafe);
82
+ } else {
83
+ t.throws(assertUpgSafe);
84
+ }
85
+ });
86
+ }
87
+
88
+ testValid('SafeContract', 'transparent', true);
89
+ testValid('SafeContractWithFreeFunctionCall', 'transparent', true);
90
+ testValid('SafeContractWithLibraryCall', 'transparent', true);
91
+ testValid('SafeContractWithLibraryImport', 'transparent', true);
92
+ testValid('SafeContractWithLibraryUsingFor', 'transparent', true);
93
+ testValid('SafeContractWithTransitiveLibraryCall', 'transparent', true);
94
+ testValid('SafeContractWithParentCall', 'transparent', true);
95
+
96
+ testValid('RiskyParentContract', 'transparent', false);
97
+
98
+ testValid('UnsafeContract', 'transparent', false);
99
+ testValid('UnsafeContractWithFreeFunctionCall', 'transparent', false);
100
+ testValid('UnsafeContractWithLibraryCall', 'transparent', false);
101
+ testValid('UnsafeContractWithLibraryUsingFor', 'transparent', false);
102
+ testValid('UnsafeContractWithTransitiveLibraryCall', 'transparent', false);
103
+ testValid('UnsafeContractWithParentCall', 'transparent', false);
104
+ testValid('UnsafeContractWithInheritedParent', 'transparent', false);
105
+ testValid('UnsafeContractWithInheritedTransitiveParent', 'transparent', false);
106
+
107
+ testValid('UnsafeAllow', 'transparent', false);
108
+ testValid('AllowReachable', 'transparent', true);
109
+ testValid('UnsafeAllowReachableDifferentOpcode', 'transparent', false);
110
+
111
+ testValid('UnsafeAllowParent', 'transparent', false);
112
+ testValid('AllowReachableParent', 'transparent', true);
113
+ testValid('UnsafeAllowReachableParentDifferentOpcode', 'transparent', false);
114
+
115
+ testValid('AllowChildInheritedTransitive', 'transparent', true);
116
+ testValid('AllowChild', 'transparent', true);
117
+ testValid('AllowChildCallTransitive', 'transparent', true);
118
+
119
+ testValid('AllowChildSelfReachable', 'transparent', true);
120
+
121
+ testValid('SafeRecursion', 'transparent', true);
122
+ testValid('UnsafeRecursion', 'transparent', false);
123
+
124
+ testValid('UnsafeChild1', 'transparent', false);
125
+ testValid('UnsafeChild2', 'transparent', false);
126
+ testValid('UnsafeChild3', 'transparent', false);
127
+ testValid('UnsafeChild4', 'transparent', false);
128
+ testValid('AllowChild5', 'transparent', true);
129
+ testValid('AllowChild6', 'transparent', true);
130
+ testValid('UnsafeAllowChild7', 'transparent', false);
131
+ testValid('AllowReachableChild8', 'transparent', true);
132
+ testValid('UnsafeChild9', 'transparent', false);
133
+
134
+ // TODO: do not throw an error in this case
135
+ // testValid('ModifierNotUsed', 'transparent', true);
136
+
137
+ testValid('ModifierUsed', 'transparent', false);