@@ -95,6 +95,8 @@ class OutputsStmt extends Statement instanceof YamlMapping {
95
95
this .( YamlMapping ) .lookup ( name ) .( YamlMapping ) .lookup ( "value" ) = result or
96
96
this .( YamlMapping ) .lookup ( name ) = result
97
97
}
98
+
99
+ string getAnOutputName ( ) { this .( YamlMapping ) .maps ( any ( YamlString s | s .getValue ( ) = result ) , _) }
98
100
}
99
101
100
102
class InputExpr extends Expression instanceof YamlString {
@@ -158,6 +160,10 @@ class JobStmt extends Statement instanceof Actions::Job {
158
160
* arg1: value1
159
161
*/
160
162
JobUsesExpr getUsesExpr ( ) { result .getJobStmt ( ) = this }
163
+
164
+ predicate usesReusableWorkflow ( ) {
165
+ this .( YamlMapping ) .maps ( any ( YamlString s | s .getValue ( ) = "uses" ) , _)
166
+ }
161
167
}
162
168
163
169
/**
@@ -353,106 +359,164 @@ class ExprAccessExpr extends Expression instanceof YamlString {
353
359
string getExpression ( ) { result = expr }
354
360
355
361
JobStmt getJobStmt ( ) { result .getAChildNode * ( ) = this }
362
+ }
363
+
364
+ /**
365
+ * A context access expression.
366
+ * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
367
+ */
368
+ class CtxAccessExpr extends ExprAccessExpr {
369
+ CtxAccessExpr ( ) {
370
+ expr .regexpMatch ( [
371
+ stepsCtxRegex ( ) , needsCtxRegex ( ) , jobsCtxRegex ( ) , envCtxRegex ( ) , inputsCtxRegex ( )
372
+ ] )
373
+ }
374
+
375
+ abstract string getFieldName ( ) ;
356
376
357
377
abstract Expression getRefExpr ( ) ;
358
378
}
359
379
380
+ private string stepsCtxRegex ( ) { result = "steps\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)" }
381
+
382
+ private string needsCtxRegex ( ) { result = "needs\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)" }
383
+
384
+ private string jobsCtxRegex ( ) { result = "jobs\\.([A-Za-z0-9_-]+)\\.outputs\\.([A-Za-z0-9_-]+)" }
385
+
386
+ private string envCtxRegex ( ) { result = "env\\.([A-Za-z0-9_-]+)" }
387
+
388
+ private string inputsCtxRegex ( ) { result = "inputs\\.([A-Za-z0-9_-]+)" }
389
+
360
390
/**
361
- * Holds for an ExprAccessExpr accesing the `steps` context.
391
+ * Holds for an expression accesing the `steps` context.
362
392
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
363
393
* e.g. `${{ steps.changed-files.outputs.all_changed_files }}`
364
394
*/
365
- class StepOutputAccessExpr extends ExprAccessExpr {
395
+ class StepsCtxAccessExpr extends CtxAccessExpr {
366
396
string stepId ;
367
- string varName ;
397
+ string fieldName ;
368
398
369
- StepOutputAccessExpr ( ) {
370
- stepId =
371
- this .getExpression ( ) .regexpCapture ( "steps\\.([A-Za-z0-9_-]+)\\.outputs\\.[A-Za-z0-9_-]+" , 1 ) and
372
- varName =
373
- this .getExpression ( ) .regexpCapture ( "steps\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)" , 1 )
399
+ StepsCtxAccessExpr ( ) {
400
+ expr .regexpMatch ( stepsCtxRegex ( ) ) and
401
+ stepId = expr .regexpCapture ( stepsCtxRegex ( ) , 1 ) and
402
+ fieldName = expr .regexpCapture ( stepsCtxRegex ( ) , 2 )
374
403
}
375
404
405
+ override string getFieldName ( ) { result = fieldName }
406
+
376
407
override Expression getRefExpr ( ) {
377
408
this .getLocation ( ) .getFile ( ) = result .getLocation ( ) .getFile ( ) and
378
409
result .( StepStmt ) .getId ( ) = stepId
379
410
}
380
411
}
381
412
382
413
/**
383
- * Holds for an ExprAccessExpr accesing the `needs` or `job` contexts.
414
+ * Holds for an expression accesing the `needs` context.
415
+ * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
416
+ * e.g. `${{ needs.job1.outputs.foo}}`
417
+ */
418
+ class NeedsCtxAccessExpr extends CtxAccessExpr {
419
+ JobStmt job ;
420
+ string jobId ;
421
+ string fieldName ;
422
+
423
+ NeedsCtxAccessExpr ( ) {
424
+ expr .regexpMatch ( needsCtxRegex ( ) ) and
425
+ jobId = expr .regexpCapture ( needsCtxRegex ( ) , 1 ) and
426
+ fieldName = expr .regexpCapture ( needsCtxRegex ( ) , 2 ) and
427
+ job .getId ( ) = jobId
428
+ }
429
+
430
+ predicate usesReusableWorkflow ( ) { job .usesReusableWorkflow ( ) }
431
+
432
+ override string getFieldName ( ) { result = fieldName }
433
+
434
+ override Expression getRefExpr ( ) {
435
+ job .getLocation ( ) .getFile ( ) = this .getLocation ( ) .getFile ( ) and
436
+ (
437
+ // regular jobs
438
+ job .getOutputStmt ( ) .getOutputExpr ( fieldName ) = result
439
+ or
440
+ // jobs calling reusable workflows
441
+ job .getUsesExpr ( ) = result
442
+ )
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Holds for an expression accesing the `jobs` context.
384
448
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
385
- * e.g. `${{ needs.job1.outputs.foo}}` or `${{ jobs.job1.outputs.foo}}` (for reusable workflows)
449
+ * e.g. `${{ jobs.job1.outputs.foo}}` (within reusable workflows)
386
450
*/
387
- class JobOutputAccessExpr extends ExprAccessExpr {
451
+ class JobsCtxAccessExpr extends CtxAccessExpr {
388
452
string jobId ;
389
- string varName ;
390
-
391
- JobOutputAccessExpr ( ) {
392
- jobId =
393
- this .getExpression ( )
394
- .regexpCapture ( "(needs|jobs)\\.([A-Za-z0-9_-]+)\\.outputs\\.[A-Za-z0-9_-]+" , 2 ) and
395
- varName =
396
- this .getExpression ( )
397
- .regexpCapture ( "(needs|jobs)\\.[A-Za-z0-9_-]+\\.outputs\\.([A-Za-z0-9_-]+)" , 2 )
453
+ string fieldName ;
454
+
455
+ JobsCtxAccessExpr ( ) {
456
+ expr .regexpMatch ( jobsCtxRegex ( ) ) and
457
+ jobId = expr .regexpCapture ( jobsCtxRegex ( ) , 1 ) and
458
+ fieldName = expr .regexpCapture ( jobsCtxRegex ( ) , 2 )
398
459
}
399
460
461
+ override string getFieldName ( ) { result = fieldName }
462
+
400
463
override Expression getRefExpr ( ) {
401
464
exists ( JobStmt job |
402
465
job .getId ( ) = jobId and
403
466
job .getLocation ( ) .getFile ( ) = this .getLocation ( ) .getFile ( ) and
404
- (
405
- // A Job can have multiple outputs, so we need to check both
406
- // jobs.<job_id>.outputs.<output_name>
407
- job .getOutputStmt ( ) .getOutputExpr ( varName ) = result
408
- or
409
- // jobs.<job_id>.uses (variables returned from the reusable workflow
410
- job .getUsesExpr ( ) = result
411
- )
467
+ job .getOutputStmt ( ) .getOutputExpr ( fieldName ) = result
412
468
)
413
469
}
414
470
}
415
471
416
472
/**
417
- * Holds for an ExprAccessExpr accesing the `inputs` context.
473
+ * Holds for an expression the `inputs` context.
418
474
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
419
475
* e.g. `${{ inputs.foo }}`
420
476
*/
421
- class InputAccessExpr extends ExprAccessExpr {
422
- string paramName ;
477
+ class InputsCtxAccessExpr extends CtxAccessExpr {
478
+ string fieldName ;
423
479
424
- InputAccessExpr ( ) {
425
- paramName = this .getExpression ( ) .regexpCapture ( "inputs\\.([A-Za-z0-9_-]+)" , 1 )
480
+ InputsCtxAccessExpr ( ) {
481
+ expr .regexpMatch ( inputsCtxRegex ( ) ) and
482
+ fieldName = expr .regexpCapture ( inputsCtxRegex ( ) , 1 )
426
483
}
427
484
485
+ override string getFieldName ( ) { result = fieldName }
486
+
428
487
override Expression getRefExpr ( ) {
429
488
exists ( ReusableWorkflowStmt w |
430
489
w .getLocation ( ) .getFile ( ) = this .getLocation ( ) .getFile ( ) and
431
- w .getInputsStmt ( ) .getInputExpr ( paramName ) = result
490
+ w .getInputsStmt ( ) .getInputExpr ( fieldName ) = result
432
491
)
433
492
or
434
493
exists ( CompositeActionStmt a |
435
494
a .getLocation ( ) .getFile ( ) = this .getLocation ( ) .getFile ( ) and
436
- a .getInputsStmt ( ) .getInputExpr ( paramName ) = result
495
+ a .getInputsStmt ( ) .getInputExpr ( fieldName ) = result
437
496
)
438
497
}
439
498
}
440
499
441
500
/**
442
- * Holds for an ExprAccessExpr accesing the `env` context.
501
+ * Holds for an expression accesing the `env` context.
443
502
* https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
444
503
* e.g. `${{ env.foo }}`
445
504
*/
446
- class EnvAccessExpr extends ExprAccessExpr {
447
- string varName ;
505
+ class EnvCtxAccessExpr extends CtxAccessExpr {
506
+ string fieldName ;
507
+
508
+ EnvCtxAccessExpr ( ) {
509
+ expr .regexpMatch ( envCtxRegex ( ) ) and
510
+ fieldName = expr .regexpCapture ( envCtxRegex ( ) , 1 )
511
+ }
448
512
449
- EnvAccessExpr ( ) { varName = this . getExpression ( ) . regexpCapture ( "env\\.([A-Za-z0-9_-]+)" , 1 ) }
513
+ override string getFieldName ( ) { result = fieldName }
450
514
451
515
override Expression getRefExpr ( ) {
452
- exists ( JobUsesExpr s | s .getEnvExpr ( varName ) = result )
516
+ exists ( JobUsesExpr s | s .getEnvExpr ( fieldName ) = result )
453
517
or
454
- exists ( StepUsesExpr s | s .getEnvExpr ( varName ) = result )
518
+ exists ( StepUsesExpr s | s .getEnvExpr ( fieldName ) = result )
455
519
or
456
- exists ( RunExpr s | s .getEnvExpr ( varName ) = result )
520
+ exists ( RunExpr s | s .getEnvExpr ( fieldName ) = result )
457
521
}
458
522
}
0 commit comments