import * as fs from 'fs'; import * as path from 'path'; import { CodeQL } from './codeql'; import * as configUtils from './config-utils'; import { isTracedLanguage, Language } from './languages'; import * as util from './util'; export type TracerConfig = { spec: string; env: { [key: string]: string }; }; const CRITICAL_TRACER_VARS = new Set( ['SEMMLE_PRELOAD_libtrace', , 'SEMMLE_RUNNER', , 'SEMMLE_COPY_EXECUTABLES_ROOT', , 'SEMMLE_DEPTRACE_SOCKET', , 'SEMMLE_JAVA_TOOL_OPTIONS' ]); export async function getTracerConfigForLanguage( codeql: CodeQL, config: configUtils.Config, language: Language): Promise { const env = await codeql.getTracerEnv(util.getCodeQLDatabasePath(config.tempDir, language)); const spec = env['ODASA_TRACER_CONFIGURATION']; const info: TracerConfig = { spec, env: {} }; // Extract critical tracer variables from the environment for (let entry of Object.entries(env)) { const key = entry[0]; const value = entry[1]; // skip ODASA_TRACER_CONFIGURATION as it is handled separately if (key === 'ODASA_TRACER_CONFIGURATION') { continue; } // skip undefined values if (typeof value === 'undefined') { continue; } // Keep variables that do not exist in current environment. In addition always keep // critical and CODEQL_ variables if (typeof process.env[key] === 'undefined' || CRITICAL_TRACER_VARS.has(key) || key.startsWith('CODEQL_')) { info.env[key] = value; } } return info; } export function concatTracerConfigs( tracerConfigs: { [lang: string]: TracerConfig }, config: configUtils.Config): TracerConfig { // A tracer config is a map containing additional environment variables and a tracer 'spec' file. // A tracer 'spec' file has the following format [log_file, number_of_blocks, blocks_text] // Merge the environments const env: { [key: string]: string; } = {}; let copyExecutables = false; let envSize = 0; for (const v of Object.values(tracerConfigs)) { for (let e of Object.entries(v.env)) { const name = e[0]; const value = e[1]; // skip SEMMLE_COPY_EXECUTABLES_ROOT as it is handled separately if (name === 'SEMMLE_COPY_EXECUTABLES_ROOT') { copyExecutables = true; } else if (name in env) { if (env[name] !== value) { throw Error('Incompatible values in environment parameter ' + name + ': ' + env[name] + ' and ' + value); } } else { env[name] = value; envSize += 1; } } } // Concatenate spec files into a new spec file let languages = Object.keys(tracerConfigs); const cppIndex = languages.indexOf('cpp'); // Make sure cpp is the last language, if it's present since it must be concatenated last if (cppIndex !== -1) { let lastLang = languages[languages.length - 1]; languages[languages.length - 1] = languages[cppIndex]; languages[cppIndex] = lastLang; } let totalLines: string[] = []; let totalCount = 0; for (let lang of languages) { const lines = fs.readFileSync(tracerConfigs[lang].spec, 'utf8').split(/\r?\n/); const count = parseInt(lines[1], 10); totalCount += count; totalLines.push(...lines.slice(2)); } const newLogFilePath = path.resolve(config.tempDir, 'compound-build-tracer.log'); const spec = path.resolve(config.tempDir, 'compound-spec'); const compoundTempFolder = path.resolve(config.tempDir, 'compound-temp'); const newSpecContent = [newLogFilePath, totalCount.toString(10), ...totalLines]; if (copyExecutables) { env['SEMMLE_COPY_EXECUTABLES_ROOT'] = compoundTempFolder; envSize += 1; } fs.writeFileSync(spec, newSpecContent.join('\n')); // Prepare the content of the compound environment file let buffer = Buffer.alloc(4); buffer.writeInt32LE(envSize, 0); for (let e of Object.entries(env)) { const key = e[0]; const value = e[1]; const lineBuffer = new Buffer(key + '=' + value + '\0', 'utf8'); const sizeBuffer = Buffer.alloc(4); sizeBuffer.writeInt32LE(lineBuffer.length, 0); buffer = Buffer.concat([buffer, sizeBuffer, lineBuffer]); } // Write the compound environment const envPath = spec + '.environment'; fs.writeFileSync(envPath, buffer); return { env, spec }; } export async function getCombinedTracerConfig( config: configUtils.Config, codeql: CodeQL): Promise { // Abort if there are no traced languages as there's nothing to do const tracedLanguages = config.languages.filter(isTracedLanguage); if (tracedLanguages.length === 0) { return undefined; } // Get all the tracer configs and combine them together const tracedLanguageConfigs: { [lang: string]: TracerConfig } = {}; for (const language of tracedLanguages) { tracedLanguageConfigs[language] = await getTracerConfigForLanguage(codeql, config, language); } const mainTracerConfig = concatTracerConfigs(tracedLanguageConfigs, config); // Add a couple more variables mainTracerConfig.env['ODASA_TRACER_CONFIGURATION'] = mainTracerConfig.spec; const codeQLDir = path.dirname(codeql.getPath()); if (process.platform === 'darwin') { mainTracerConfig.env['DYLD_INSERT_LIBRARIES'] = path.join(codeQLDir, 'tools', 'osx64', 'libtrace.dylib'); } else if (process.platform !== 'win32') { mainTracerConfig.env['LD_PRELOAD'] = path.join(codeQLDir, 'tools', 'linux64', '${LIB}trace.so'); } return mainTracerConfig; }