Locating Variables

Locating Attributes and Uniforms

getAttribLocation(program, name) returns an attribute's slot, from 0 to MAX_VERTEX_ATTRIBS - 1 (16 here), or -1. getUniformLocation(program, name) returns an opaque WebGLUniformLocation or null, never -1, so the common check if (location < 0) never fires for a uniform. Both miss variables that are declared but unused, because the linker drops them. getActiveUniform() and getActiveAttrib() list what survived:

Listing a program's active uniforms, and three missesJavaScript
const program = GLH.program(gl, `#version 300 es
in vec2 aPosition; in vec3 aUnused; uniform float uNotUsed;
void main() { gl_Position = vec4(aPosition, 0.0, 1.0); }`, `#version 300 es
precision mediump float; out vec4 outColor; struct Light { vec3 color; float power; };
uniform Light uLight; uniform vec3 uColors[6];
void main() { outColor = vec4(uLight.color * uLight.power + uColors[5], 1.0); }`);
for (let i = 0; i < gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); i++) {
  const { name, type, size } = gl.getActiveUniform(program, i);
  console.log(name, type === gl.FLOAT_VEC3 ? 'vec3' : type, 'x' + size);
}
console.log(gl.getAttribLocation(program, 'aUnused'),
  gl.getUniformLocation(program, 'uNotUsed'), gl.getUniformLocation(program, 'uColor'));
Output
uColors[0] vec3 x6
uLight.color vec3 x1
uLight.power 5126 x1
-1 null null

Types come back as enums (5126 is FLOAT); a structure lists one uniform per field. To fix an attribute's slot, write layout(location = 1) in vec3 aColor; or call bindAttribLocation() before linking. Look locations up once, never in the render loop.

Listing a program's active uniforms, showing the three lookups that miss, and drawing with the restHTMLLive
<!doctype html>
<style>
  body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
  .row { display: flex; gap: 10px; max-width: 600px; align-items: flex-start; }
  canvas { width: 300px; height: 200px; flex: none; }
  table { border-collapse: collapse; flex: 1; font-family: monospace; }
  td, th { padding: 3px 5px; border-bottom: 1px solid #e3ddd0; text-align: left; }
  .miss td { color: #b5452f; }
</style>
<div class="row">
  <canvas id="view" width="300" height="200"></canvas>
  <table id="list"></table>
</div>
<script>
const gl = document.getElementById('view').getContext('webgl2');
function program(vsSource, fsSource) {
  const p = gl.createProgram();
  for (const [type, src] of [[gl.VERTEX_SHADER, vsSource], [gl.FRAGMENT_SHADER, fsSource]]) {
    const s = gl.createShader(type);
    gl.shaderSource(s, src); gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
    gl.attachShader(p, s);
  }
  gl.linkProgram(p);
  return p;
}
const prog = program(`#version 300 es
in vec2 aPosition; in vec3 aUnused; uniform float uNotUsed;   // declared, never used
out vec2 vUV;
void main() { vUV = aPosition * 0.5 + 0.5; gl_Position = vec4(aPosition, 0.0, 1.0); }`, `#version 300 es
precision mediump float; out vec4 outColor; in vec2 vUV;
struct Light { vec3 color; float power; };
uniform Light uLight; uniform vec3 uColors[6];
void main() {
  int book = int(vUV.x * 6.0);
  float inCover = step(0.12, fract(vUV.x * 6.0)) * step(0.1, vUV.y) * step(vUV.y, 0.9);
  vec3 cover = uColors[book] * uLight.color * uLight.power * (0.7 + 0.5 * vUV.y);
  outColor = vec4(mix(vec3(0.93, 0.91, 0.87), cover, inCover), 1.0);
}`);
gl.useProgram(prog);

// What survived linking: a struct lists one uniform per field; an array reports its size.
const typeNames = { [gl.FLOAT]: 'float', [gl.FLOAT_VEC3]: 'vec3' };
let rows = '<tr><th>active uniform</th><th>type</th><th>size</th></tr>';
for (let i = 0; i < gl.getProgramParameter(prog, gl.ACTIVE_UNIFORMS); i++) {
  const { name, type, size } = gl.getActiveUniform(prog, i);
  rows += `<tr><td>${name}</td><td>${typeNames[type] || type}</td><td>${size}</td></tr>`;
}
rows += `<tr class="miss"><td>getAttribLocation('aUnused')</td><td colspan="2">${gl.getAttribLocation(prog, 'aUnused')}</td></tr>`;
rows += `<tr class="miss"><td>getUniformLocation('uNotUsed')</td><td colspan="2">${gl.getUniformLocation(prog, 'uNotUsed')}</td></tr>`;
rows += `<tr class="miss"><td>getUniformLocation('uColor')</td><td colspan="2">${gl.getUniformLocation(prog, 'uColor')} (typo)</td></tr>`;
document.getElementById('list').innerHTML = rows;

// Look locations up once, outside any render loop.
const loc = {
  aPosition: gl.getAttribLocation(prog, 'aPosition'),
  lightColor: gl.getUniformLocation(prog, 'uLight.color'),   // struct fields by full name
  lightPower: gl.getUniformLocation(prog, 'uLight.power'),
  colors: gl.getUniformLocation(prog, 'uColors'),             // same as 'uColors[0]'
};
gl.uniform3f(loc.lightColor, 1.0, 0.95, 0.85);
gl.uniform1f(loc.lightPower, 1.15);
gl.uniform3fv(loc.colors, [0.12,0.37,0.55, 0.36,0.25,0.6, 0.88,0.6,0.06, 0.25,0.49,0.23, 0.71,0.27,0.18, 0.16,0.62,0.56]);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1,-1, 1,-1, 1,1, -1,-1, 1,1, -1,1]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc.aPosition);
gl.vertexAttribPointer(loc.aPosition, 2, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
</script>