11private import codeql.actions.ast.internal.Yaml
22private import codeql.Locations
3+ private import codeql.actions.Ast:: Utils as Utils
34
45/**
56 * Gets the length of each line in the StringValue .
@@ -18,24 +19,19 @@ int partialLineLengthSum(string text, int i) {
1819 result = sum ( int j , int length | j in [ 0 .. i ] and length = lineLength ( text , j ) | length )
1920}
2021
21- /**
22- * Holds if `${{ e }}` is a GitHub Actions expression evaluated within this YAML string.
23- * See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
24- * Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
25- * Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
26- */
27- string getASimpleReferenceExpression ( YamlString s , int offset ) {
22+ string getADelimitedExpression ( YamlString s , int offset ) {
2823 // We use `regexpFind` to obtain *all* matches of `${{...}}`,
2924 // not just the last (greedy match) or first (reluctant match).
3025 result =
3126 s .getValue ( )
32- .regexpFind ( "\\$\\{\\{\\s*[A-Za-z0-9_\\[\\]\\*\\(\\)\\.\\-]+\\s*\\}\\}" , _, offset )
33- .regexpCapture ( "(\\$\\{\\{\\s*[A-Za-z0-9_\\[\\]\\*\\((\\)\\.\\-]+\\s*\\}\\})" , 1 )
27+ .regexpFind ( "\\$\\{\\{(?:[^}]|}(?!}))*\\}\\}" , _, offset )
28+ .regexpCapture ( "(\\$\\{\\{(?:[^}]|}(?!}))*\\}\\})" , 1 )
29+ .trim ( )
3430}
3531
3632private newtype TAstNode =
3733 TExpressionNode ( YamlNode key , YamlScalar value , string raw , int exprOffset ) {
38- raw = getASimpleReferenceExpression ( value , exprOffset ) and
34+ raw = getADelimitedExpression ( value , exprOffset ) and
3935 exists ( YamlMapping m |
4036 (
4137 exists ( int i | value = m .getValueNode ( i ) and key = m .getKeyNode ( i ) )
@@ -45,6 +41,14 @@ private newtype TAstNode =
4541 )
4642 )
4743 )
44+ or
45+ // `if`'s conditions do not need to be delimted with ${{}}
46+ exists ( YamlMapping m |
47+ m .maps ( key , value ) and
48+ key .( YamlScalar ) .getValue ( ) = [ "if" ] and
49+ value .getValue ( ) = raw and
50+ exprOffset = 1
51+ )
4852 } or
4953 TCompositeAction ( YamlMapping n ) {
5054 n instanceof YamlDocument and
@@ -123,7 +127,7 @@ class ScalarValueImpl extends AstNodeImpl, TScalarValueNode {
123127
124128 override Location getLocation ( ) { result = value .getLocation ( ) }
125129
126- override YamlNode getNode ( ) { result = value }
130+ override YamlScalar getNode ( ) { result = value }
127131}
128132
129133class ExpressionImpl extends AstNodeImpl , TExpressionNode {
@@ -135,15 +139,16 @@ class ExpressionImpl extends AstNodeImpl, TExpressionNode {
135139
136140 ExpressionImpl ( ) {
137141 this = TExpressionNode ( key , value , rawExpression , exprOffset - 1 ) and
138- expression =
139- rawExpression .regexpCapture ( "\\$\\{\\{\\s*([A-Za-z0-9_\\[\\]\\*\\((\\)\\.\\-]+)\\s*\\}\\}" , 1 )
142+ if rawExpression .trim ( ) .regexpMatch ( "\\$\\{\\{.*\\}\\}" )
143+ then expression = rawExpression .trim ( ) .regexpCapture ( "\\$\\{\\{\\s*(.*)\\s*\\}\\}" , 1 ) .trim ( )
144+ else expression = rawExpression .trim ( )
140145 }
141146
142147 override string toString ( ) { result = expression }
143148
144149 override AstNodeImpl getAChildNode ( ) { none ( ) }
145150
146- override AstNodeImpl getParentNode ( ) { result .getNode ( ) = value }
151+ override ScalarValueImpl getParentNode ( ) { result .getNode ( ) = value }
147152
148153 override string getAPrimaryQlClass ( ) { result = "ExpressionNode" }
149154
@@ -638,6 +643,9 @@ class IfImpl extends AstNodeImpl, TIfNode {
638643
639644 /** Gets the condition that must be satisfied for this job to run. */
640645 string getCondition ( ) { result = n .( YamlScalar ) .getValue ( ) }
646+
647+ /** Gets the condition that must be satisfied for this job to run. */
648+ ExpressionImpl getConditionExpr ( ) { result .getParentNode ( ) .getNode ( ) = n }
641649}
642650
643651class EnvImpl extends AstNodeImpl , TEnvNode {
@@ -776,11 +784,29 @@ class RunImpl extends StepImpl {
776784 }
777785}
778786
787+ /**
788+ * Holds if `${{ e }}` is a GitHub Actions expression evaluated within this YAML string.
789+ * See https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions.
790+ * Only finds simple expressions like `${{ github.event.comment.body }}`, where the expression contains only alphanumeric characters, underscores, dots, or dashes.
791+ * Does not identify more complicated expressions like `${{ fromJSON(env.time) }}`, or ${{ format('{{Hello {0}!}}', github.event.head_commit.author.name) }}
792+ */
793+ bindingset [ s]
794+ string getASimpleReferenceExpression ( string s , int offset ) {
795+ // We use `regexpFind` to obtain *all* matches of `${{...}}`,
796+ // not just the last (greedy match) or first (reluctant match).
797+ result =
798+ s .trim ( )
799+ .regexpFind ( "[A-Za-z0-9'\"_\\[\\]\\*\\(\\)\\.\\-]+" , _, offset )
800+ .regexpCapture ( "([A-Za-z0-9'\"_\\[\\]\\*\\(\\)\\.\\-]+)" , 1 )
801+ }
802+
779803/**
780804 * A ${{}} expression accessing a context variable such as steps, needs, jobs, env, inputs, or matrix.
781805 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
782806 */
783- abstract class ContextExpressionImpl extends ExpressionImpl {
807+ abstract class SimpleReferenceExpressionImpl extends ExpressionImpl {
808+ SimpleReferenceExpressionImpl ( ) { exists ( getASimpleReferenceExpression ( expression , _) ) }
809+
784810 abstract string getFieldName ( ) ;
785811
786812 abstract AstNodeImpl getTarget ( ) ;
@@ -816,44 +842,49 @@ private string wrapRegexp(string regex) {
816842 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
817843 * e.g. `${{ steps.changed-files.outputs.all_changed_files }}`
818844 */
819- class StepsExpressionImpl extends ContextExpressionImpl {
845+ class StepsExpressionImpl extends SimpleReferenceExpressionImpl {
820846 string stepId ;
821847 string fieldName ;
822848
823849 StepsExpressionImpl ( ) {
824- expression .regexpMatch ( stepsCtxRegex ( ) ) and
825- stepId = expression .regexpCapture ( stepsCtxRegex ( ) , 1 ) and
826- fieldName = expression .regexpCapture ( stepsCtxRegex ( ) , 2 )
850+ Utils :: normalizeExpr ( expression ) .regexpMatch ( stepsCtxRegex ( ) ) and
851+ stepId = Utils :: normalizeExpr ( expression ) .regexpCapture ( stepsCtxRegex ( ) , 1 ) and
852+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( stepsCtxRegex ( ) , 2 )
827853 }
828854
829855 override string getFieldName ( ) { result = fieldName }
830856
831857 override AstNodeImpl getTarget ( ) {
832- this .getLocation ( ) . getFile ( ) = result .getLocation ( ) . getFile ( ) and
858+ this .getEnclosingJob ( ) = result .getEnclosingJob ( ) and
833859 result .( StepImpl ) .getId ( ) = stepId
834860 }
861+
862+ string getStepId ( ) { result = stepId }
835863}
836864
837865/**
838866 * Holds for an expression accesing the `needs` context.
839867 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
840868 * e.g. `${{ needs.job1.outputs.foo}}`
841869 */
842- class NeedsExpressionImpl extends ContextExpressionImpl {
870+ class NeedsExpressionImpl extends SimpleReferenceExpressionImpl {
843871 JobImpl neededJob ;
844872 string fieldName ;
845873
846874 NeedsExpressionImpl ( ) {
847- expression .regexpMatch ( needsCtxRegex ( ) ) and
848- fieldName = expression .regexpCapture ( needsCtxRegex ( ) , 2 ) and
849- neededJob .getId ( ) = expression .regexpCapture ( needsCtxRegex ( ) , 1 ) and
875+ Utils :: normalizeExpr ( expression ) .regexpMatch ( needsCtxRegex ( ) ) and
876+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( needsCtxRegex ( ) , 2 ) and
877+ neededJob .getId ( ) = Utils :: normalizeExpr ( expression ) .regexpCapture ( needsCtxRegex ( ) , 1 ) and
850878 neededJob .getLocation ( ) .getFile ( ) = this .getLocation ( ) .getFile ( )
851879 }
852880
853881 override string getFieldName ( ) { result = fieldName }
854882
855883 override AstNodeImpl getTarget ( ) {
856- this .getEnclosingJob ( ) .getANeededJob ( ) = neededJob and
884+ (
885+ this .getEnclosingJob ( ) .getANeededJob ( ) = neededJob or
886+ this .getEnclosingJob ( ) = neededJob
887+ ) and
857888 (
858889 // regular jobs
859890 neededJob .getOutputs ( ) = result
@@ -869,14 +900,14 @@ class NeedsExpressionImpl extends ContextExpressionImpl {
869900 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
870901 * e.g. `${{ jobs.job1.outputs.foo}}` (within reusable workflows)
871902 */
872- class JobsExpressionImpl extends ContextExpressionImpl {
903+ class JobsExpressionImpl extends SimpleReferenceExpressionImpl {
873904 string jobId ;
874905 string fieldName ;
875906
876907 JobsExpressionImpl ( ) {
877- expression .regexpMatch ( jobsCtxRegex ( ) ) and
878- jobId = expression .regexpCapture ( jobsCtxRegex ( ) , 1 ) and
879- fieldName = expression .regexpCapture ( jobsCtxRegex ( ) , 2 )
908+ Utils :: normalizeExpr ( expression ) .regexpMatch ( jobsCtxRegex ( ) ) and
909+ jobId = Utils :: normalizeExpr ( expression ) .regexpCapture ( jobsCtxRegex ( ) , 1 ) and
910+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( jobsCtxRegex ( ) , 2 )
880911 }
881912
882913 override string getFieldName ( ) { result = fieldName }
@@ -895,12 +926,12 @@ class JobsExpressionImpl extends ContextExpressionImpl {
895926 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
896927 * e.g. `${{ inputs.foo }}`
897928 */
898- class InputsExpressionImpl extends ContextExpressionImpl {
929+ class InputsExpressionImpl extends SimpleReferenceExpressionImpl {
899930 string fieldName ;
900931
901932 InputsExpressionImpl ( ) {
902- expression .regexpMatch ( inputsCtxRegex ( ) ) and
903- fieldName = expression .regexpCapture ( inputsCtxRegex ( ) , 1 )
933+ Utils :: normalizeExpr ( expression ) .regexpMatch ( inputsCtxRegex ( ) ) and
934+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( inputsCtxRegex ( ) , 1 )
904935 }
905936
906937 override string getFieldName ( ) { result = fieldName }
@@ -920,12 +951,12 @@ class InputsExpressionImpl extends ContextExpressionImpl {
920951 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
921952 * e.g. `${{ env.foo }}`
922953 */
923- class EnvExpressionImpl extends ContextExpressionImpl {
954+ class EnvExpressionImpl extends SimpleReferenceExpressionImpl {
924955 string fieldName ;
925956
926957 EnvExpressionImpl ( ) {
927- expression .regexpMatch ( envCtxRegex ( ) ) and
928- fieldName = expression .regexpCapture ( envCtxRegex ( ) , 1 )
958+ Utils :: normalizeExpr ( expression ) .regexpMatch ( envCtxRegex ( ) ) and
959+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( envCtxRegex ( ) , 1 )
929960 }
930961
931962 override string getFieldName ( ) { result = fieldName }
@@ -943,12 +974,12 @@ class EnvExpressionImpl extends ContextExpressionImpl {
943974 * https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
944975 * e.g. `${{ matrix.foo }}`
945976 */
946- class MatrixExpressionImpl extends ContextExpressionImpl {
977+ class MatrixExpressionImpl extends SimpleReferenceExpressionImpl {
947978 string fieldName ;
948979
949980 MatrixExpressionImpl ( ) {
950- expression .regexpMatch ( matrixCtxRegex ( ) ) and
951- fieldName = expression .regexpCapture ( matrixCtxRegex ( ) , 1 )
981+ Utils :: normalizeExpr ( expression ) .regexpMatch ( matrixCtxRegex ( ) ) and
982+ fieldName = Utils :: normalizeExpr ( expression ) .regexpCapture ( matrixCtxRegex ( ) , 1 )
952983 }
953984
954985 override string getFieldName ( ) { result = fieldName }
0 commit comments