• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 #include <sstream>
7 #include <string>
8 #include <vector>
9 #include "GLSLANG/ShaderLang.h"
10 #include "angle_gl.h"
11 #include "gtest/gtest.h"
12 
13 class ExpressionLimitTest : public testing::Test
14 {
15   protected:
16     static const int kMaxExpressionComplexity = 16;
17     static const int kMaxCallStackDepth       = 16;
18     static const int kMaxFunctionParameters   = 16;
19     static const char *kExpressionTooComplex;
20     static const char *kCallStackTooDeep;
21     static const char *kHasRecursion;
22     static const char *kTooManyParameters;
23     static const char *kTooComplexSwitch;
24     static const char *kGlobalVariableInit;
25 
SetUp()26     virtual void SetUp()
27     {
28         memset(&resources, 0, sizeof(resources));
29 
30         GenerateResources(&resources);
31     }
32 
33     // Set up the per compile resources
GenerateResources(ShBuiltInResources * res)34     static void GenerateResources(ShBuiltInResources *res)
35     {
36         sh::InitBuiltInResources(res);
37 
38         res->MaxVertexAttribs             = 8;
39         res->MaxVertexUniformVectors      = 128;
40         res->MaxVaryingVectors            = 8;
41         res->MaxVertexTextureImageUnits   = 0;
42         res->MaxCombinedTextureImageUnits = 8;
43         res->MaxTextureImageUnits         = 8;
44         res->MaxFragmentUniformVectors    = 16;
45         res->MaxDrawBuffers               = 1;
46 
47         res->OES_standard_derivatives = 0;
48         res->OES_EGL_image_external   = 0;
49 
50         res->MaxExpressionComplexity = kMaxExpressionComplexity;
51         res->MaxCallStackDepth       = kMaxCallStackDepth;
52         res->MaxFunctionParameters   = kMaxFunctionParameters;
53     }
54 
GenerateLongExpression(int length,std::stringstream * ss)55     static void GenerateLongExpression(int length, std::stringstream *ss)
56     {
57         for (int ii = 0; ii < length; ++ii)
58         {
59             *ss << "+ vec4(" << ii << ")";
60         }
61     }
62 
GenerateShaderWithLongExpression(int length)63     static std::string GenerateShaderWithLongExpression(int length)
64     {
65         static const char *shaderStart =
66             R"(precision mediump float;
67             uniform vec4 u_color;
68             void main()
69             {
70                gl_FragColor = u_color
71         )";
72 
73         std::stringstream ss;
74         ss << shaderStart;
75         GenerateLongExpression(length, &ss);
76         ss << "; }";
77 
78         return ss.str();
79     }
80 
GenerateShaderWithUnusedLongExpression(int length)81     static std::string GenerateShaderWithUnusedLongExpression(int length)
82     {
83         static const char *shaderStart =
84             R"(precision mediump float;
85             uniform vec4 u_color;
86             void main()
87             {
88                gl_FragColor = u_color;
89             }
90             vec4 someFunction() {
91               return u_color
92         )";
93 
94         std::stringstream ss;
95 
96         ss << shaderStart;
97         GenerateLongExpression(length, &ss);
98         ss << "; }";
99 
100         return ss.str();
101     }
102 
GenerateDeepFunctionStack(int length,std::stringstream * ss)103     static void GenerateDeepFunctionStack(int length, std::stringstream *ss)
104     {
105         static const char *shaderStart =
106             R"(precision mediump float;
107             uniform vec4 u_color;
108             vec4 function0()  {
109               return u_color;
110             }
111         )";
112 
113         *ss << shaderStart;
114         for (int ii = 0; ii < length; ++ii)
115         {
116             *ss << "vec4 function" << (ii + 1) << "() {\n"
117                 << "  return function" << ii << "();\n"
118                 << "}\n";
119         }
120     }
121 
GenerateShaderWithDeepFunctionStack(int length)122     static std::string GenerateShaderWithDeepFunctionStack(int length)
123     {
124         std::stringstream ss;
125 
126         GenerateDeepFunctionStack(length, &ss);
127 
128         ss << "void main() {\n"
129            << "  gl_FragColor = function" << length << "();\n"
130            << "}";
131 
132         return ss.str();
133     }
134 
GenerateShaderWithUnusedDeepFunctionStack(int length)135     static std::string GenerateShaderWithUnusedDeepFunctionStack(int length)
136     {
137         std::stringstream ss;
138 
139         GenerateDeepFunctionStack(length, &ss);
140 
141         ss << "void main() {\n"
142            << "  gl_FragColor = vec4(0,0,0,0);\n"
143            << "}";
144 
145         return ss.str();
146     }
147 
GenerateShaderWithFunctionParameters(int parameters)148     static std::string GenerateShaderWithFunctionParameters(int parameters)
149     {
150         std::stringstream ss;
151 
152         ss << "precision mediump float;\n"
153            << "\n"
154            << "float foo(";
155         for (int i = 0; i < parameters; ++i)
156         {
157             ss << "float f" << i;
158             if (i + 1 < parameters)
159             {
160                 ss << ", ";
161             }
162         }
163         ss << ")\n"
164            << "{\n"
165            << "    return f0;\n"
166            << "}\n"
167            << "\n"
168            << "void main()\n"
169            << "{\n"
170            << "    gl_FragColor = vec4(0,0,0,0);\n"
171            << "}";
172 
173         return ss.str();
174     }
175 
GenerateShaderWithNestingInsideSwitch(int nesting)176     static std::string GenerateShaderWithNestingInsideSwitch(int nesting)
177     {
178         std::stringstream shaderString;
179         shaderString <<
180             R"(#version 300 es
181             uniform int u;
182 
183             void main()
184             {
185                 int x;
186                 switch (u)
187                 {
188                     case 0:
189                         x = x)";
190         for (int i = 0; i < nesting; ++i)
191         {
192             shaderString << " + x";
193         }
194         shaderString <<
195             R"(;
196                 }  // switch (u)
197             })";
198         return shaderString.str();
199     }
200 
GenerateShaderWithNestingInsideGlobalInitializer(int nesting)201     static std::string GenerateShaderWithNestingInsideGlobalInitializer(int nesting)
202     {
203         std::stringstream shaderString;
204         shaderString <<
205             R"(uniform int u;
206             int x = u)";
207 
208         for (int i = 0; i < nesting; ++i)
209         {
210             shaderString << " + u";
211         }
212         shaderString << R"(;
213             void main()
214             {
215                 gl_FragColor = vec4(0.0);
216             })";
217         return shaderString.str();
218     }
219 
220     // Compiles a shader and if there's an error checks for a specific
221     // substring in the error log. This way we know the error is specific
222     // to the issue we are testing.
CheckShaderCompilation(ShHandle compiler,const char * source,ShCompileOptions compileOptions,const char * expected_error)223     bool CheckShaderCompilation(ShHandle compiler,
224                                 const char *source,
225                                 ShCompileOptions compileOptions,
226                                 const char *expected_error)
227     {
228         bool success = sh::Compile(compiler, &source, 1, compileOptions) != 0;
229         if (success)
230         {
231             success = !expected_error;
232         }
233         else
234         {
235             std::string log = sh::GetInfoLog(compiler);
236             if (expected_error)
237                 success = log.find(expected_error) != std::string::npos;
238 
239             EXPECT_TRUE(success) << log << "\n----shader----\n" << source;
240         }
241         return success;
242     }
243 
244     ShBuiltInResources resources;
245 };
246 
247 const char *ExpressionLimitTest::kExpressionTooComplex = "Expression too complex";
248 const char *ExpressionLimitTest::kCallStackTooDeep     = "Call stack too deep";
249 const char *ExpressionLimitTest::kHasRecursion =
250     "Recursive function call in the following call chain";
251 const char *ExpressionLimitTest::kTooManyParameters = "Function has too many parameters";
252 const char *ExpressionLimitTest::kTooComplexSwitch =
253     "too complex expressions inside a switch statement";
254 const char *ExpressionLimitTest::kGlobalVariableInit =
255     "global variable initializers must be constant expressions";
256 
TEST_F(ExpressionLimitTest,ExpressionComplexity)257 TEST_F(ExpressionLimitTest, ExpressionComplexity)
258 {
259     ShShaderSpec spec       = SH_WEBGL_SPEC;
260     ShShaderOutput output   = SH_ESSL_OUTPUT;
261     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
262     ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
263 
264     // Test expression under the limit passes.
265     EXPECT_TRUE(CheckShaderCompilation(
266         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity - 10).c_str(),
267         compileOptions, nullptr));
268     // Test expression over the limit fails.
269     EXPECT_TRUE(CheckShaderCompilation(
270         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(),
271         compileOptions, kExpressionTooComplex));
272     // Test expression over the limit without a limit does not fail.
273     EXPECT_TRUE(CheckShaderCompilation(
274         vertexCompiler, GenerateShaderWithLongExpression(kMaxExpressionComplexity + 10).c_str(),
275         compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr));
276     sh::Destruct(vertexCompiler);
277 }
278 
TEST_F(ExpressionLimitTest,UnusedExpressionComplexity)279 TEST_F(ExpressionLimitTest, UnusedExpressionComplexity)
280 {
281     ShShaderSpec spec       = SH_WEBGL_SPEC;
282     ShShaderOutput output   = SH_ESSL_OUTPUT;
283     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
284     ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
285 
286     // Test expression under the limit passes.
287     EXPECT_TRUE(CheckShaderCompilation(
288         vertexCompiler,
289         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity - 10).c_str(),
290         compileOptions, nullptr));
291     // Test expression over the limit fails.
292     EXPECT_TRUE(CheckShaderCompilation(
293         vertexCompiler,
294         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(),
295         compileOptions, kExpressionTooComplex));
296     // Test expression over the limit without a limit does not fail.
297     EXPECT_TRUE(CheckShaderCompilation(
298         vertexCompiler,
299         GenerateShaderWithUnusedLongExpression(kMaxExpressionComplexity + 10).c_str(),
300         compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr));
301     sh::Destruct(vertexCompiler);
302 }
303 
TEST_F(ExpressionLimitTest,CallStackDepth)304 TEST_F(ExpressionLimitTest, CallStackDepth)
305 {
306     ShShaderSpec spec       = SH_WEBGL_SPEC;
307     ShShaderOutput output   = SH_ESSL_OUTPUT;
308     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
309     ShCompileOptions compileOptions = SH_LIMIT_CALL_STACK_DEPTH;
310 
311     // Test call stack under the limit passes.
312     EXPECT_TRUE(CheckShaderCompilation(
313         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth - 10).c_str(),
314         compileOptions, nullptr));
315     // Test call stack over the limit fails.
316     EXPECT_TRUE(CheckShaderCompilation(
317         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
318         compileOptions, kCallStackTooDeep));
319     // Test call stack over the limit without limit does not fail.
320     EXPECT_TRUE(CheckShaderCompilation(
321         vertexCompiler, GenerateShaderWithDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
322         compileOptions & ~SH_LIMIT_CALL_STACK_DEPTH, nullptr));
323     sh::Destruct(vertexCompiler);
324 }
325 
TEST_F(ExpressionLimitTest,UnusedCallStackDepth)326 TEST_F(ExpressionLimitTest, UnusedCallStackDepth)
327 {
328     ShShaderSpec spec       = SH_WEBGL_SPEC;
329     ShShaderOutput output   = SH_ESSL_OUTPUT;
330     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
331     ShCompileOptions compileOptions = SH_LIMIT_CALL_STACK_DEPTH;
332 
333     // Test call stack under the limit passes.
334     EXPECT_TRUE(CheckShaderCompilation(
335         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth - 10).c_str(),
336         compileOptions, nullptr));
337     // Test call stack over the limit fails.
338     EXPECT_TRUE(CheckShaderCompilation(
339         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
340         compileOptions, kCallStackTooDeep));
341     // Test call stack over the limit without limit does not fail.
342     EXPECT_TRUE(CheckShaderCompilation(
343         vertexCompiler, GenerateShaderWithUnusedDeepFunctionStack(kMaxCallStackDepth + 10).c_str(),
344         compileOptions & ~SH_LIMIT_CALL_STACK_DEPTH, nullptr));
345     sh::Destruct(vertexCompiler);
346 }
347 
TEST_F(ExpressionLimitTest,Recursion)348 TEST_F(ExpressionLimitTest, Recursion)
349 {
350     ShShaderSpec spec       = SH_WEBGL_SPEC;
351     ShShaderOutput output   = SH_ESSL_OUTPUT;
352     ShHandle vertexCompiler = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
353     ShCompileOptions compileOptions = 0;
354 
355     static const char *shaderWithRecursion0 =
356         R"(precision mediump float;
357         uniform vec4 u_color;
358         vec4 someFunc()  {
359             return someFunc();
360         }
361 
362         void main() {
363             gl_FragColor = u_color * someFunc();
364         }
365     )";
366 
367     static const char *shaderWithRecursion1 =
368         R"(precision mediump float;
369         uniform vec4 u_color;
370 
371         vec4 someFunc();
372 
373         vec4 someFunc1()  {
374             return someFunc();
375         }
376 
377         vec4 someFunc()  {
378             return someFunc1();
379         }
380 
381         void main() {
382             gl_FragColor = u_color * someFunc();
383         }
384     )";
385 
386     static const char *shaderWithRecursion2 =
387         R"(precision mediump float;
388         uniform vec4 u_color;
389         vec4 someFunc()  {
390             if (u_color.x > 0.5) {
391                 return someFunc();
392             } else {
393                 return vec4(1);
394             }
395         }
396 
397         void main() {
398             gl_FragColor = someFunc();
399         }
400     )";
401 
402     static const char *shaderWithRecursion3 =
403         R"(precision mediump float;
404         uniform vec4 u_color;
405         vec4 someFunc()  {
406             if (u_color.x > 0.5) {
407                 return vec4(1);
408             } else {
409                 return someFunc();
410             }
411         }
412 
413         void main() {
414             gl_FragColor = someFunc();
415         }
416     )";
417 
418     static const char *shaderWithRecursion4 =
419         R"(precision mediump float;
420         uniform vec4 u_color;
421         vec4 someFunc()  {
422             return (u_color.x > 0.5) ? vec4(1) : someFunc();
423         }
424 
425         void main() {
426             gl_FragColor = someFunc();
427         }
428     )";
429 
430     static const char *shaderWithRecursion5 =
431         R"(precision mediump float;
432         uniform vec4 u_color;
433         vec4 someFunc()  {
434             return (u_color.x > 0.5) ? someFunc() : vec4(1);
435         }
436 
437         void main() {
438             gl_FragColor = someFunc();
439         }
440     )";
441 
442     static const char *shaderWithRecursion6 =
443         R"(precision mediump float;
444         uniform vec4 u_color;
445         vec4 someFunc()  {
446             return someFunc();
447         }
448 
449         void main() {
450             gl_FragColor = u_color;
451         }
452     )";
453 
454     static const char *shaderWithNoRecursion =
455         R"(precision mediump float;
456         uniform vec4 u_color;
457 
458         vec3 rgb(int r, int g, int b) {
459             return vec3(float(r) / 255.0, float(g) / 255.0, float(b) / 255.0);
460         }
461 
462         void main() {
463             vec3 hairColor0 = rgb(151, 200, 234);
464             vec3 faceColor2 = rgb(183, 148, 133);
465             gl_FragColor = u_color + vec4(hairColor0 + faceColor2, 0);
466         }
467     )";
468 
469     static const char *shaderWithRecursion7 =
470         R"(precision mediump float;
471         uniform vec4 u_color;
472 
473         vec4 function2() {
474             return u_color;
475         }
476 
477         vec4 function1() {
478             vec4 a = function2();
479             vec4 b = function1();
480             return a + b;
481         }
482 
483         void main() {
484             gl_FragColor = function1();
485         }
486     )";
487 
488     static const char *shaderWithRecursion8 =
489         R"(precision mediump float;
490         uniform vec4 u_color;
491 
492         vec4 function1();
493 
494         vec4 function3() {
495             return function1();
496         }
497 
498         vec4 function2() {
499             return function3();
500         }
501 
502         vec4 function1() {
503             return function2();
504         }
505 
506         void main() {
507             gl_FragColor = function1();
508         }
509     )";
510 
511     // Check simple recursions fails.
512     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion0, compileOptions,
513                                        kHasRecursion));
514     // Check simple recursions fails.
515     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion1, compileOptions,
516                                        kHasRecursion));
517     // Check if recursions fails.
518     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion2, compileOptions,
519                                        kHasRecursion));
520     // Check if recursions fails.
521     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion3, compileOptions,
522                                        kHasRecursion));
523     // Check ternary recursions fails.
524     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion4, compileOptions,
525                                        kHasRecursion));
526     // Check ternary recursions fails.
527     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion5, compileOptions,
528                                        kHasRecursion));
529 
530     // Check some more forms of recursion
531     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6, compileOptions,
532                                        kHasRecursion));
533     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion7, compileOptions,
534                                        kHasRecursion));
535     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion8, compileOptions,
536                                        kHasRecursion));
537     // Check unused recursions fails if limiting call stack
538     // since we check all paths.
539     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithRecursion6,
540                                        compileOptions | SH_LIMIT_CALL_STACK_DEPTH, kHasRecursion));
541 
542     // Check unused recursions passes.
543     EXPECT_TRUE(
544         CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion, compileOptions, nullptr));
545     // Check unused recursions passes if limiting call stack.
546     EXPECT_TRUE(CheckShaderCompilation(vertexCompiler, shaderWithNoRecursion,
547                                        compileOptions | SH_LIMIT_CALL_STACK_DEPTH, nullptr));
548     sh::Destruct(vertexCompiler);
549 }
550 
TEST_F(ExpressionLimitTest,FunctionParameterCount)551 TEST_F(ExpressionLimitTest, FunctionParameterCount)
552 {
553     ShShaderSpec spec     = SH_WEBGL_SPEC;
554     ShShaderOutput output = SH_ESSL_OUTPUT;
555     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
556     ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
557 
558     // Test parameters under the limit succeeds.
559     EXPECT_TRUE(CheckShaderCompilation(
560         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters).c_str(),
561         compileOptions, nullptr));
562     // Test parameters over the limit fails.
563     EXPECT_TRUE(CheckShaderCompilation(
564         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(),
565         compileOptions, kTooManyParameters));
566     // Test parameters over the limit without limit does not fail.
567     EXPECT_TRUE(CheckShaderCompilation(
568         compiler, GenerateShaderWithFunctionParameters(kMaxFunctionParameters + 1).c_str(),
569         compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr));
570     sh::Destruct(compiler);
571 }
572 
TEST_F(ExpressionLimitTest,NestingInsideSwitch)573 TEST_F(ExpressionLimitTest, NestingInsideSwitch)
574 {
575     ShShaderSpec spec     = SH_WEBGL2_SPEC;
576     ShShaderOutput output = SH_ESSL_OUTPUT;
577     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
578     ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
579 
580     // Test nesting over the limit fails.
581     EXPECT_TRUE(CheckShaderCompilation(
582         compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(),
583         compileOptions, kExpressionTooComplex));
584     // Test nesting over the limit without limit does not fail.
585     EXPECT_TRUE(CheckShaderCompilation(
586         compiler, GenerateShaderWithNestingInsideSwitch(kMaxExpressionComplexity + 1).c_str(),
587         compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr));
588     // Test that nesting way over the limit doesn't cause stack overflow but is handled
589     // gracefully.
590     EXPECT_TRUE(CheckShaderCompilation(compiler,
591                                        GenerateShaderWithNestingInsideSwitch(5000).c_str(),
592                                        compileOptions, kTooComplexSwitch));
593     sh::Destruct(compiler);
594 }
595 
TEST_F(ExpressionLimitTest,NestingInsideGlobalInitializer)596 TEST_F(ExpressionLimitTest, NestingInsideGlobalInitializer)
597 {
598     ShShaderSpec spec     = SH_WEBGL_SPEC;
599     ShShaderOutput output = SH_ESSL_OUTPUT;
600     ShHandle compiler     = sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &resources);
601     ShCompileOptions compileOptions = SH_LIMIT_EXPRESSION_COMPLEXITY;
602 
603     // Test nesting over the limit fails.
604     EXPECT_TRUE(CheckShaderCompilation(
605         compiler,
606         GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(),
607         compileOptions, kExpressionTooComplex));
608     // Test nesting over the limit without limit does not fail.
609     EXPECT_TRUE(CheckShaderCompilation(
610         compiler,
611         GenerateShaderWithNestingInsideGlobalInitializer(kMaxExpressionComplexity + 1).c_str(),
612         compileOptions & ~SH_LIMIT_EXPRESSION_COMPLEXITY, nullptr));
613     // Test that nesting way over the limit doesn't cause stack overflow but is handled
614     // gracefully.
615     EXPECT_TRUE(CheckShaderCompilation(
616         compiler, GenerateShaderWithNestingInsideGlobalInitializer(5000).c_str(), compileOptions,
617         kGlobalVariableInit));
618     sh::Destruct(compiler);
619 }
620